Compare commits

...

4 Commits

Author SHA1 Message Date
Smaug123
8f0e8d6369 Failing test 2025-09-17 08:51:36 +01:00
Smaug123
e7780dc412 Docs 2025-09-17 08:49:23 +01:00
Smaug123
c3311bd350 README 2025-09-17 08:43:33 +01:00
Smaug123
07e9eaff38 Implement 2025-09-17 08:37:41 +01:00
13 changed files with 256 additions and 133 deletions

View File

@@ -1,5 +1,9 @@
Notable changes are recorded here. Notable changes are recorded here.
# WoofWare.Myriad.Plugins 8.1.1
Adds `GenerateCapturingMock`, which is `GenerateMock` but additionally records the calls made to each function.
# WoofWare.Myriad.Plugins 8.0.3 # WoofWare.Myriad.Plugins 8.0.3
The RestEase-style HTTP client generator now automatically adds the `application/json` content type header to requests which are POSTing a body that is known to be JSON-serialised. The RestEase-style HTTP client generator now automatically adds the `application/json` content type header to requests which are POSTing a body that is known to be JSON-serialised.

View File

@@ -5,7 +5,7 @@ open WoofWare.Myriad.Plugins
[<GenerateCapturingMock>] [<GenerateCapturingMock>]
type IPublicType = type IPublicType =
abstract Mem1 : string * int -> string list abstract Mem1 : foo : string * int -> string list
abstract Mem2 : string -> int abstract Mem2 : string -> int
abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string
@@ -36,9 +36,9 @@ type VeryPublicType<'a, 'b> =
[<GenerateCapturingMock>] [<GenerateCapturingMock>]
type Curried<'a> = type Curried<'a> =
abstract Mem1 : int -> 'a -> string abstract Mem1 : bar : int -> 'a -> string
abstract Mem2 : int * string -> 'a -> string abstract Mem2 : int * string -> baz : 'a -> string
abstract Mem3 : (int * string) -> 'a -> string abstract Mem3 : quux : (int * string) -> flurb : 'a -> string
abstract Mem4 : (int * string) -> ('a * int) -> string abstract Mem4 : (int * string) -> ('a * int) -> string
abstract Mem5 : x : int * string -> ('a * int) -> string abstract Mem5 : x : int * string -> ('a * int) -> string
abstract Mem6 : int * string -> y : 'a * int -> string abstract Mem6 : int * string -> y : 'a * int -> string

View File

@@ -13,10 +13,13 @@ open WoofWare.Myriad.Plugins
type internal PublicTypeMock = type internal PublicTypeMock =
{ {
Mem1 : string * int -> string list Mem1 : string * int -> string list
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
Mem3 : int * option<System.Threading.CancellationToken> -> string Mem3 : int * option<System.Threading.CancellationToken> -> string
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken> Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken>
} }
@@ -44,10 +47,13 @@ open WoofWare.Myriad.Plugins
type public PublicTypeInternalFalseMock = type public PublicTypeInternalFalseMock =
{ {
Mem1 : string * int -> string list Mem1 : string * int -> string list
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
Mem3 : int * option<System.Threading.CancellationToken> -> string Mem3 : int * option<System.Threading.CancellationToken> -> string
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken> Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken>
} }
@@ -75,8 +81,10 @@ open WoofWare.Myriad.Plugins
type internal InternalTypeMock = type internal InternalTypeMock =
{ {
Mem1 : string * int -> unit Mem1 : string * int -> unit
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
} }
@@ -101,8 +109,10 @@ open WoofWare.Myriad.Plugins
type private PrivateTypeMock = type private PrivateTypeMock =
{ {
Mem1 : string * int -> unit Mem1 : string * int -> unit
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
} }
@@ -127,8 +137,10 @@ open WoofWare.Myriad.Plugins
type private PrivateTypeInternalFalseMock = type private PrivateTypeInternalFalseMock =
{ {
Mem1 : string * int -> unit Mem1 : string * int -> unit
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
} }
@@ -153,6 +165,7 @@ open WoofWare.Myriad.Plugins
type internal VeryPublicTypeMock<'a, 'b> = type internal VeryPublicTypeMock<'a, 'b> =
{ {
Mem1 : 'a -> 'b Mem1 : 'a -> 'b
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<'a> Mem1_Calls : ResizeArray<'a>
} }
@@ -170,63 +183,64 @@ namespace SomeNamespace.CapturingMock
open System open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// A single call to the Mem1 method module internal CurriedMockCalls =
type internal Mem1Call<'a> = /// A single call to the Mem1 method
{ type internal Mem1Call<'a> =
arg0 : int {
arg1 : 'a bar : int
} Arg1 : 'a
}
/// A single call to the Mem2 method /// A single call to the Mem2 method
type internal Mem2Call<'a> = type internal Mem2Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a baz : 'a
} }
/// A single call to the Mem3 method /// A single call to the Mem3 method
type internal Mem3Call<'a> = type internal Mem3Call<'a> =
{ {
arg0 : int * string quux : (int * string)
arg1 : 'a flurb : 'a
} }
/// A single call to the Mem4 method /// A single call to the Mem4 method
type internal Mem4Call<'a> = type internal Mem4Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a * int Arg1 : 'a * int
} }
/// A single call to the Mem5 method /// A single call to the Mem5 method
type internal Mem5Call<'a> = type internal Mem5Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a * int Arg1 : 'a * int
} }
/// A single call to the Mem6 method /// A single call to the Mem6 method
type internal Mem6Call<'a> = type internal Mem6Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a * int Arg1 : 'a * int
} }
/// Mock record type for an interface /// Mock record type for an interface
type internal CurriedMock<'a> = type internal CurriedMock<'a> =
{ {
Mem1 : int -> 'a -> string Mem1 : int -> 'a -> string
Mem1_Calls : ResizeArray<Mem1Call<'a>> Mem1_Calls : ResizeArray<CurriedMockCalls.Mem1Call<'a>>
Mem2 : int * string -> 'a -> string Mem2 : int * string -> 'a -> string
Mem2_Calls : ResizeArray<Mem2Call<'a>> Mem2_Calls : ResizeArray<CurriedMockCalls.Mem2Call<'a>>
Mem3 : (int * string) -> 'a -> string Mem3 : (int * string) -> 'a -> string
Mem3_Calls : ResizeArray<Mem3Call<'a>> Mem3_Calls : ResizeArray<CurriedMockCalls.Mem3Call<'a>>
Mem4 : (int * string) -> ('a * int) -> string Mem4 : (int * string) -> ('a * int) -> string
Mem4_Calls : ResizeArray<Mem4Call<'a>> Mem4_Calls : ResizeArray<CurriedMockCalls.Mem4Call<'a>>
Mem5 : int * string -> ('a * int) -> string Mem5 : int * string -> ('a * int) -> string
Mem5_Calls : ResizeArray<Mem5Call<'a>> Mem5_Calls : ResizeArray<CurriedMockCalls.Mem5Call<'a>>
Mem6 : int * string -> 'a * int -> string Mem6 : int * string -> 'a * int -> string
Mem6_Calls : ResizeArray<Mem6Call<'a>> Mem6_Calls : ResizeArray<CurriedMockCalls.Mem6Call<'a>>
} }
/// An implementation where every non-unit method throws. /// An implementation where every non-unit method throws.
@@ -249,7 +263,7 @@ type internal CurriedMock<'a> =
interface Curried<'a> with interface Curried<'a> with
member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0) member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0)
member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0) member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0)
member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0) member this.Mem3 arg_0_0 arg_1_0 = this.Mem3 (arg_0_0) (arg_1_0)
member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) = member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) =
this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
@@ -270,8 +284,10 @@ type internal TypeWithInterfaceMock =
/// Implementation of IDisposable.Dispose /// Implementation of IDisposable.Dispose
Dispose : unit -> unit Dispose : unit -> unit
Mem1 : string option -> string[] Async Mem1 : string option -> string[] Async
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string option> Mem1_Calls : ResizeArray<string option>
Mem2 : unit -> string[] Async Mem2 : unit -> string[] Async
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<unit> Mem2_Calls : ResizeArray<unit>
} }
@@ -302,10 +318,13 @@ type internal TypeWithPropertiesMock =
/// Implementation of IDisposable.Dispose /// Implementation of IDisposable.Dispose
Dispose : unit -> unit Dispose : unit -> unit
Prop1 : unit -> int Prop1 : unit -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Prop1_Calls : ResizeArray<unit> Prop1_Calls : ResizeArray<unit>
Prop2 : unit -> unit Async Prop2 : unit -> unit Async
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Prop2_Calls : ResizeArray<unit> Prop2_Calls : ResizeArray<unit>
Mem1 : string option -> string[] Async Mem1 : string option -> string[] Async
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string option> Mem1_Calls : ResizeArray<string option>
} }

View File

@@ -12,10 +12,13 @@ open System
type internal PublicTypeNoAttrMock = type internal PublicTypeNoAttrMock =
{ {
Mem1 : string * int -> string list Mem1 : string * int -> string list
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
Mem3 : int * option<System.Threading.CancellationToken> -> string Mem3 : int * option<System.Threading.CancellationToken> -> string
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken> Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken>
} }
@@ -42,10 +45,13 @@ open System
type public PublicTypeInternalFalseNoAttrMock = type public PublicTypeInternalFalseNoAttrMock =
{ {
Mem1 : string * int -> string list Mem1 : string * int -> string list
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
Mem3 : int * option<System.Threading.CancellationToken> -> string Mem3 : int * option<System.Threading.CancellationToken> -> string
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken> Mem3_Calls : ResizeArray<int * System.Threading.CancellationToken>
} }
@@ -72,8 +78,10 @@ open System
type internal InternalTypeNoAttrMock = type internal InternalTypeNoAttrMock =
{ {
Mem1 : string * int -> unit Mem1 : string * int -> unit
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
} }
@@ -97,8 +105,10 @@ open System
type private PrivateTypeNoAttrMock = type private PrivateTypeNoAttrMock =
{ {
Mem1 : string * int -> unit Mem1 : string * int -> unit
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
} }
@@ -122,8 +132,10 @@ open System
type private PrivateTypeInternalFalseNoAttrMock = type private PrivateTypeInternalFalseNoAttrMock =
{ {
Mem1 : string * int -> unit Mem1 : string * int -> unit
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string * int> Mem1_Calls : ResizeArray<string * int>
Mem2 : string -> int Mem2 : string -> int
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<string> Mem2_Calls : ResizeArray<string>
} }
@@ -147,6 +159,7 @@ open System
type internal VeryPublicTypeNoAttrMock<'a, 'b> = type internal VeryPublicTypeNoAttrMock<'a, 'b> =
{ {
Mem1 : 'a -> 'b Mem1 : 'a -> 'b
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<'a> Mem1_Calls : ResizeArray<'a>
} }
@@ -163,63 +176,64 @@ namespace SomeNamespace.CapturingMock
open System open System
/// A single call to the Mem1 method module internal CurriedNoAttrMockCalls =
type internal Mem1Call<'a> = /// A single call to the Mem1 method
{ type internal Mem1Call<'a> =
arg0 : int {
arg1 : 'a Arg0 : int
} Arg1 : 'a
}
/// A single call to the Mem2 method /// A single call to the Mem2 method
type internal Mem2Call<'a> = type internal Mem2Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a Arg1 : 'a
} }
/// A single call to the Mem3 method /// A single call to the Mem3 method
type internal Mem3Call<'a> = type internal Mem3Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a Arg1 : 'a
} }
/// A single call to the Mem4 method /// A single call to the Mem4 method
type internal Mem4Call<'a> = type internal Mem4Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a * int Arg1 : 'a * int
} }
/// A single call to the Mem5 method /// A single call to the Mem5 method
type internal Mem5Call<'a> = type internal Mem5Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a * int Arg1 : 'a * int
} }
/// A single call to the Mem6 method /// A single call to the Mem6 method
type internal Mem6Call<'a> = type internal Mem6Call<'a> =
{ {
arg0 : int * string Arg0 : int * string
arg1 : 'a * int Arg1 : 'a * int
} }
/// Mock record type for an interface /// Mock record type for an interface
type internal CurriedNoAttrMock<'a> = type internal CurriedNoAttrMock<'a> =
{ {
Mem1 : int -> 'a -> string Mem1 : int -> 'a -> string
Mem1_Calls : ResizeArray<Mem1Call<'a>> Mem1_Calls : ResizeArray<CurriedNoAttrMockCalls.Mem1Call<'a>>
Mem2 : int * string -> 'a -> string Mem2 : int * string -> 'a -> string
Mem2_Calls : ResizeArray<Mem2Call<'a>> Mem2_Calls : ResizeArray<CurriedNoAttrMockCalls.Mem2Call<'a>>
Mem3 : (int * string) -> 'a -> string Mem3 : (int * string) -> 'a -> string
Mem3_Calls : ResizeArray<Mem3Call<'a>> Mem3_Calls : ResizeArray<CurriedNoAttrMockCalls.Mem3Call<'a>>
Mem4 : (int * string) -> ('a * int) -> string Mem4 : (int * string) -> ('a * int) -> string
Mem4_Calls : ResizeArray<Mem4Call<'a>> Mem4_Calls : ResizeArray<CurriedNoAttrMockCalls.Mem4Call<'a>>
Mem5 : int * string -> ('a * int) -> string Mem5 : int * string -> ('a * int) -> string
Mem5_Calls : ResizeArray<Mem5Call<'a>> Mem5_Calls : ResizeArray<CurriedNoAttrMockCalls.Mem5Call<'a>>
Mem6 : int * string -> 'a * int -> string Mem6 : int * string -> 'a * int -> string
Mem6_Calls : ResizeArray<Mem6Call<'a>> Mem6_Calls : ResizeArray<CurriedNoAttrMockCalls.Mem6Call<'a>>
} }
/// An implementation where every non-unit method throws. /// An implementation where every non-unit method throws.
@@ -262,8 +276,10 @@ type internal TypeWithInterfaceNoAttrMock =
/// Implementation of IDisposable.Dispose /// Implementation of IDisposable.Dispose
Dispose : unit -> unit Dispose : unit -> unit
Mem1 : string option -> string[] Async Mem1 : string option -> string[] Async
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem1_Calls : ResizeArray<string option> Mem1_Calls : ResizeArray<string option>
Mem2 : unit -> string[] Async Mem2 : unit -> string[] Async
/// Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it.
Mem2_Calls : ResizeArray<unit> Mem2_Calls : ResizeArray<unit>
} }

View File

@@ -13,7 +13,7 @@ Currently implemented:
* `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods). * `JsonParse` (to stamp out `jsonParse : JsonNode -> 'T` methods).
* `JsonSerialize` (to stamp out `toJsonNode : 'T -> JsonNode` methods). * `JsonSerialize` (to stamp out `toJsonNode : 'T -> JsonNode` methods).
* `HttpClient` (to stamp out a [RestEase](https://github.com/canton7/RestEase)-style HTTP client). * `HttpClient` (to stamp out a [RestEase](https://github.com/canton7/RestEase)-style HTTP client).
* `GenerateMock` (to stamp out a record type corresponding to an interface, like a compile-time [Foq](https://github.com/fsprojects/Foq)). * `GenerateMock` and `GenerateCapturingMock` (to stamp out a record type corresponding to an interface, like a compile-time [Foq](https://github.com/fsprojects/Foq)).
* `ArgParser` (to stamp out a basic argument parser). * `ArgParser` (to stamp out a basic argument parser).
* `SwaggerClient` (to stamp out an HTTP client for a Swagger API). * `SwaggerClient` (to stamp out an HTTP client for a Swagger API).
* `CreateCatamorphism` (to stamp out a non-stack-overflowing [catamorphism](https://fsharpforfunandprofit.com/posts/recursive-types-and-folds/) for a discriminated union). * `CreateCatamorphism` (to stamp out a non-stack-overflowing [catamorphism](https://fsharpforfunandprofit.com/posts/recursive-types-and-folds/) for a discriminated union).
@@ -440,9 +440,9 @@ There are also some design decisions:
so arguments are forced to be tupled. so arguments are forced to be tupled.
* The `[<Optional>]` attribute is not supported and will probably not be supported, because I consider it to be cursed. * The `[<Optional>]` attribute is not supported and will probably not be supported, because I consider it to be cursed.
## `GenerateMock` ## `GenerateMock` and `GenerateCapturingMock`
Takes a type like this: `GenerateMock` takes a type like this:
```fsharp ```fsharp
[<GenerateMock>] [<GenerateMock>]
@@ -472,6 +472,48 @@ type internal PublicTypeMock =
member this.Mem2 (arg0) = this.Mem2 (arg0) member this.Mem2 (arg0) = this.Mem2 (arg0)
``` ```
`GenerateCapturingMock` additionally captures the calls made to each function (except for `Dispose`).
It takes a type like this:
```fsharp
[<GenerateCapturingMock>]
type IPublicType =
abstract Mem1 : string * int -> string list
abstract Mem2 : baz : string -> unit -> int
```
and stamps out types like this:
```fsharp
module internal PublicTypeCalls =
type internal Mem2Call =
{
baz : string
Arg1 : unit
}
/// Mock record type for an interface
type internal PublicTypeMock =
{
Mem1 : string * int -> string list
Mem2 : string -> int
Mem2_Calls : ResizeArray<string * int>
Mem2_Calls : ResizeArray<PublicTypeCalls.Mem2Call>
}
static member Empty : PublicTypeMock =
{
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
Mem1_Calls = ResizeArray ()
Mem2_Calls = ResizeArray ()
}
interface IPublicType with
member this.Mem1 (arg0, arg1) = this.Mem1 (arg0, arg1)
member this.Mem2 (arg0) = this.Mem2 (arg0)
```
### What's the point? ### What's the point?
Reflective mocking libraries like [Foq](https://github.com/fsprojects/Foq) in my experience are a rich source of flaky tests. Reflective mocking libraries like [Foq](https://github.com/fsprojects/Foq) in my experience are a rich source of flaky tests.

View File

@@ -15,6 +15,11 @@ WoofWare.Myriad.Plugins.ArgumentLongForm inherit System.Attribute
WoofWare.Myriad.Plugins.ArgumentLongForm..ctor [constructor]: string WoofWare.Myriad.Plugins.ArgumentLongForm..ctor [constructor]: string
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute inherit System.Attribute WoofWare.Myriad.Plugins.CreateCatamorphismAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.CreateCatamorphismAttribute..ctor [constructor]: string WoofWare.Myriad.Plugins.CreateCatamorphismAttribute..ctor [constructor]: string
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute..ctor [constructor]: bool
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute..ctor [constructor]: unit
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute.DefaultIsInternal [static property]: [read-only] bool
WoofWare.Myriad.Plugins.GenerateCapturingMockAttribute.get_DefaultIsInternal [static method]: unit -> bool
WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute WoofWare.Myriad.Plugins.GenerateMockAttribute inherit System.Attribute
WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: bool WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: bool
WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: unit WoofWare.Myriad.Plugins.GenerateMockAttribute..ctor [constructor]: unit

View File

@@ -1,5 +1,5 @@
{ {
"version": "3.6", "version": "3.7",
"publicReleaseRefSpec": [ "publicReleaseRefSpec": [
"^refs/heads/main$" "^refs/heads/main$"
], ],

View File

@@ -6,7 +6,7 @@ open NUnit.Framework
open FsUnitTyped open FsUnitTyped
[<TestFixture>] [<TestFixture>]
module TestMockGenerator = module TestCapturingMockGenerator =
[<Test>] [<Test>]
let ``Example of use: IPublicType`` () = let ``Example of use: IPublicType`` () =
@@ -47,3 +47,24 @@ module TestMockGenerator =
mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |] mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |]
mock.Prop1 |> shouldEqual 44 mock.Prop1 |> shouldEqual 44
[<Test>]
let ``Example of curried use`` () =
let mock =
{ CurriedMock<string>.Empty () with
Mem1 =
fun x y ->
x |> shouldEqual 3
y |> shouldEqual "hello"
"it's me"
}
mock.Mem1 3 "hello" |> shouldEqual "it's me"
lock mock.Mem1_Calls (fun () -> Seq.toList mock.Mem1_Calls)
|> List.exactlyOne
|> shouldEqual
{
bar = 3
Arg1 = "hello"
}

View File

@@ -6,7 +6,7 @@ open NUnit.Framework
open FsUnitTyped open FsUnitTyped
[<TestFixture>] [<TestFixture>]
module TestCapturingMockGeneratorNoAttr = module TestMockGeneratorNoAttr =
[<Test>] [<Test>]
let ``Example of use: IPublicType`` () = let ``Example of use: IPublicType`` () =

View File

@@ -28,7 +28,9 @@
<Compile Include="TestHttpClient\TestVaultClient.fs" /> <Compile Include="TestHttpClient\TestVaultClient.fs" />
<Compile Include="TestHttpClient\TestVariableHeader.fs" /> <Compile Include="TestHttpClient\TestVariableHeader.fs" />
<Compile Include="TestMockGenerator\TestMockGenerator.fs" /> <Compile Include="TestMockGenerator\TestMockGenerator.fs" />
<Compile Include="TestMockGenerator\TestCapturingMockGeneratorNoAttr.fs" /> <Compile Include="TestMockGenerator\TestMockGeneratorNoAttr.fs" />
<Compile Include="TestCapturingMockGenerator\TestCapturingMockGenerator.fs" />
<Compile Include="TestCapturingMockGenerator\TestCapturingMockGeneratorNoAttr.fs" />
<Compile Include="TestJsonSerialize\TestJsonSerde.fs" /> <Compile Include="TestJsonSerialize\TestJsonSerde.fs" />
<Compile Include="TestCataGenerator\TestCataGenerator.fs" /> <Compile Include="TestCataGenerator\TestCataGenerator.fs" />
<Compile Include="TestCataGenerator\TestDirectory.fs" /> <Compile Include="TestCataGenerator\TestDirectory.fs" />
@@ -67,9 +69,4 @@
<ProjectReference Include="..\ConsumePlugin\ConsumePlugin.fsproj" /> <ProjectReference Include="..\ConsumePlugin\ConsumePlugin.fsproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="TestCapturingMockGenerator\TestCapturingMockGenerator.fs" />
<Compile Include="TestCapturingMockGenerator\TestCapturingMockGeneratorNoAttr.fs" />
</ItemGroup>
</Project> </Project>

View File

@@ -41,7 +41,12 @@ module internal CapturingInterfaceMockGenerator =
args args
|> List.mapi (fun i tupledArg -> |> List.mapi (fun i tupledArg ->
{ {
SynFieldData.Ident = $"arg%i{i}" |> Ident.create |> Some SynFieldData.Ident =
match tupledArg.Args with
| [ arg ] -> arg.Id
| _ -> None
|> Option.defaultValue (Ident.create $"Arg%i{i}")
|> Some
Attrs = [] Attrs = []
Type = Type =
tupledArg.Args tupledArg.Args
@@ -115,12 +120,7 @@ module internal CapturingInterfaceMockGenerator =
/// Builds the record field for the mock object, and also if applicable a type representing a single call to /// Builds the record field for the mock object, and also if applicable a type representing a single call to
/// that object (packaging up the args of the call). /// that object (packaging up the args of the call).
let private constructMember let private constructMember (spec : CapturingInterfaceMockOutputSpec) (mem : MemberInfo) : SynField * CallField =
(spec : CapturingInterfaceMockOutputSpec)
(generics : SynTyparDecls option)
(mem : MemberInfo)
: SynField * CallField
=
let inputType = mem.Args |> List.map constructMemberSinglePlace let inputType = mem.Args |> List.map constructMemberSinglePlace
let funcType = SynType.toFun inputType mem.ReturnType let funcType = SynType.toFun inputType mem.ReturnType
@@ -179,11 +179,11 @@ module internal CapturingInterfaceMockGenerator =
(name : string) (name : string)
(interfaceType : InterfaceType) (interfaceType : InterfaceType)
(xmlDoc : PreXmlDoc) (xmlDoc : PreXmlDoc)
: SynModuleDecl : SynModuleDecl option * SynModuleDecl
= =
let fields = let fields =
interfaceType.Members interfaceType.Members
|> List.map (constructMember spec interfaceType.Generics) |> List.map (constructMember spec)
|> List.append ( |> List.append (
interfaceType.Properties interfaceType.Properties
|> List.map constructProperty |> List.map constructProperty
@@ -252,15 +252,11 @@ module internal CapturingInterfaceMockGenerator =
let callsArrays = let callsArrays =
fields fields
|> List.map (fun (_field, extraType, fieldName) -> |> List.map (fun (_field, _, fieldName) ->
let name = SynLongIdent.createS $"{fieldName}_Calls" let name = SynLongIdent.createS $"%s{fieldName}_Calls"
let init = let init =
match extraType with SynExpr.createIdent "ResizeArray" |> SynExpr.applyTo (SynExpr.CreateConst ())
| CallField.Original _ ->
SynExpr.createIdent "ResizeArray" |> SynExpr.applyTo (SynExpr.CreateConst ())
| CallField.ArgsObject _ ->
SynExpr.createIdent "ResizeArray" |> SynExpr.applyTo (SynExpr.CreateConst ())
name, init name, init
) )
@@ -305,17 +301,23 @@ module internal CapturingInterfaceMockGenerator =
Type = SynType.app "ResizeArray" [ ty ] Type = SynType.app "ResizeArray" [ ty ]
} }
|> SynField.make |> SynField.make
| CallField.ArgsObject (name, _, generics) -> |> SynField.withDocString (
PreXmlDoc.create
"Additions to this ResizeArray are locked on itself. For maximum safety, lock on this field before reading it."
)
| CallField.ArgsObject (argsObjectName, _, generics) ->
{ {
Attrs = [] Attrs = []
Ident = Some (fieldName + "_Calls" |> Ident.create) Ident = Some (fieldName + "_Calls" |> Ident.create)
Type = Type =
match generics with match generics with
| None -> SynType.named name.idText | None -> SynType.named argsObjectName.idText
| Some generics -> | Some generics ->
generics.TyparDecls generics.TyparDecls
|> List.map (fun (SynTyparDecl.SynTyparDecl (_, typar)) -> SynType.var typar) |> List.map (fun (SynTyparDecl.SynTyparDecl (_, typar)) -> SynType.var typar)
|> SynType.app name.idText |> SynType.app' (
SynType.createLongIdent' [ $"%s{name}Calls" ; argsObjectName.idText ]
)
|> List.singleton |> List.singleton
|> SynType.app "ResizeArray" |> SynType.app "ResizeArray"
} }
@@ -445,16 +447,25 @@ module internal CapturingInterfaceMockGenerator =
let typeDecl = AstHelper.defineRecordType record let typeDecl = AstHelper.defineRecordType record
SynModuleDecl.Types ( let callsModule =
[ fields
for _, field, _ in fields do |> List.choose (fun (_, field, _) ->
match field with match field with
| CallField.Original _ -> () | CallField.Original _ -> None
| CallField.ArgsObject (_, callType, _) -> yield callType | CallField.ArgsObject (_, callType, _) -> Some callType
yield typeDecl )
], |> function
range0 | [] -> None
) | l ->
SynModuleDecl.Types (l, range0)
|> List.singleton
|> SynModuleDecl.nestedModule (
SynComponentInfo.create (Ident.create $"%s{name}Calls")
|> SynComponentInfo.withAccessibility access
)
|> Some
(callsModule, SynModuleDecl.Types ([ typeDecl ], range0))
let createRecord let createRecord
(namespaceId : LongIdent) (namespaceId : LongIdent)
@@ -476,9 +487,15 @@ module internal CapturingInterfaceMockGenerator =
s s
|> fun s -> s + "Mock" |> fun s -> s + "Mock"
let typeDecl = createType spec name interfaceType docString let callsTypes, typeDecl = createType spec name interfaceType docString
[ yield! opens |> List.map SynModuleDecl.openAny ; yield typeDecl ] [
yield! opens |> List.map SynModuleDecl.openAny
match callsTypes with
| None -> ()
| Some c -> yield c
yield typeDecl
]
|> SynModuleOrNamespace.createNamespace namespaceId |> SynModuleOrNamespace.createNamespace namespaceId
open Myriad.Core open Myriad.Core

View File

@@ -1,5 +1,7 @@
WoofWare.Myriad.Plugins.ArgParserGenerator inherit obj, implements Myriad.Core.IMyriadGenerator WoofWare.Myriad.Plugins.ArgParserGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.ArgParserGenerator..ctor [constructor]: unit WoofWare.Myriad.Plugins.ArgParserGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.CapturingInterfaceMockGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.CapturingInterfaceMockGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.CreateCatamorphismGenerator inherit obj, implements Myriad.Core.IMyriadGenerator WoofWare.Myriad.Plugins.CreateCatamorphismGenerator inherit obj, implements Myriad.Core.IMyriadGenerator
WoofWare.Myriad.Plugins.CreateCatamorphismGenerator..ctor [constructor]: unit WoofWare.Myriad.Plugins.CreateCatamorphismGenerator..ctor [constructor]: unit
WoofWare.Myriad.Plugins.HttpClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator WoofWare.Myriad.Plugins.HttpClientGenerator inherit obj, implements Myriad.Core.IMyriadGenerator

View File

@@ -1,5 +1,5 @@
{ {
"version": "8.0", "version": "8.1",
"publicReleaseRefSpec": [ "publicReleaseRefSpec": [
"^refs/heads/main$" "^refs/heads/main$"
], ],