From 277a186fda71c4678155007197f1b3bc96a7c8de Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Wed, 12 Feb 2025 23:53:15 +0000 Subject: [PATCH] Allow properties in mocked interfaces (#336) --- ConsumePlugin/GeneratedMock.fs | 31 +++++++++++++++++++ ConsumePlugin/MockExample.fs | 7 +++++ .../TestMockGenerator/TestMockGenerator.fs | 13 ++++++++ .../InterfaceMockGenerator.fs | 27 ++++++++++++++-- 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/ConsumePlugin/GeneratedMock.fs b/ConsumePlugin/GeneratedMock.fs index a1c02a3..8979349 100644 --- a/ConsumePlugin/GeneratedMock.fs +++ b/ConsumePlugin/GeneratedMock.fs @@ -206,3 +206,34 @@ type internal TypeWithInterfaceMock = interface System.IDisposable with member this.Dispose () : unit = this.Dispose () +namespace SomeNamespace + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type internal TypeWithPropertiesMock = + { + /// Implementation of IDisposable.Dispose + Dispose : unit -> unit + Prop1 : unit -> int + Prop2 : unit -> unit Async + Mem1 : string option -> string[] Async + } + + /// An implementation where every method throws. + static member Empty : TypeWithPropertiesMock = + { + Dispose = (fun () -> ()) + Prop1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop1")) + Prop2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop2")) + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + } + + interface TypeWithProperties with + member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) + member this.Prop1 = this.Prop1 () + member this.Prop2 = this.Prop2 () + + interface System.IDisposable with + member this.Dispose () : unit = this.Dispose () diff --git a/ConsumePlugin/MockExample.fs b/ConsumePlugin/MockExample.fs index fcce1a9..b5b4e7c 100644 --- a/ConsumePlugin/MockExample.fs +++ b/ConsumePlugin/MockExample.fs @@ -48,3 +48,10 @@ type TypeWithInterface = inherit IDisposable abstract Mem1 : string option -> string[] Async abstract Mem2 : unit -> string[] Async + +[] +type TypeWithProperties = + inherit IDisposable + abstract Mem1 : string option -> string[] Async + abstract Prop1 : int + abstract Prop2 : unit Async diff --git a/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGenerator.fs b/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGenerator.fs index 612d48e..eab3fc2 100644 --- a/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGenerator.fs +++ b/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGenerator.fs @@ -34,3 +34,16 @@ module TestMockGenerator = mock.Mem1 3 'a' |> shouldEqual "aaa" mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi" mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi" + + [] + let ``Example of use: properties`` () = + let mock : TypeWithProperties = + { TypeWithPropertiesMock.Empty with + Mem1 = fun i -> async { return Option.toArray i } + Prop1 = fun () -> 44 + } + :> _ + + mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |] + + mock.Prop1 |> shouldEqual 44 diff --git a/WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs b/WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs index 19289c4..a188828 100644 --- a/WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs +++ b/WoofWare.Myriad.Plugins/InterfaceMockGenerator.fs @@ -159,6 +159,15 @@ module internal InterfaceMockGenerator = |> SynMemberDefn.memberImplementation ) + let properties = + interfaceType.Properties + |> List.map (fun pi -> + SynExpr.createLongIdent' [ Ident.create "this" ; pi.Identifier ] + |> SynExpr.applyTo (SynExpr.CreateConst ()) + |> SynBinding.basic [ Ident.create "this" ; pi.Identifier ] [] + |> SynMemberDefn.memberImplementation + ) + let interfaceName = let baseName = SynType.createLongIdent interfaceType.Name @@ -174,7 +183,7 @@ module internal InterfaceMockGenerator = SynType.app' baseName generics - SynMemberDefn.Interface (interfaceName, Some range0, Some members, range0) + SynMemberDefn.Interface (interfaceName, Some range0, Some (members @ properties), range0) let access = match interfaceType.Accessibility, spec.IsInternal with @@ -248,6 +257,15 @@ module internal InterfaceMockGenerator = |> SynField.make |> SynField.withDocString (mem.XmlDoc |> Option.defaultValue PreXmlDoc.Empty) + let constructProperty (prop : PropertyInfo) : SynField = + { + Attrs = [] + Ident = Some prop.Identifier + Type = SynType.toFun [ SynType.unit ] prop.Type + } + |> SynField.make + |> SynField.withDocString (prop.XmlDoc |> Option.defaultValue PreXmlDoc.Empty) + let createRecord (namespaceId : LongIdent) (opens : SynOpenDeclTarget list) @@ -255,7 +273,12 @@ module internal InterfaceMockGenerator = : SynModuleOrNamespace = let interfaceType = AstHelper.parseInterface interfaceType - let fields = interfaceType.Members |> List.map constructMember + + let fields = + interfaceType.Members + |> List.map constructMember + |> List.append (interfaceType.Properties |> List.map constructProperty) + let docString = PreXmlDoc.create "Mock record type for an interface" let name =