Files
WoofWare.Whippet/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md
Patrick Stevens 2789152331
Some checks are pending
.NET / build (Release) (push) Waiting to run
.NET / analyzers (push) Waiting to run
.NET / build (Debug) (push) Waiting to run
.NET / check-dotnet-format (push) Waiting to run
.NET / check-nix-format (push) Waiting to run
.NET / Check links (push) Waiting to run
.NET / Check flake (push) Waiting to run
.NET / nuget-pack (push) Waiting to run
.NET / expected-pack (push) Blocked by required conditions
.NET / check-accurate-generations (push) Waiting to run
.NET / all-required-checks-complete (push) Blocked by required conditions
.NET / nuget-publish (push) Blocked by required conditions
.NET / nuget-publish-fantomas (push) Blocked by required conditions
.NET / nuget-publish-json-plugin (push) Blocked by required conditions
.NET / nuget-publish-json-attrs (push) Blocked by required conditions
.NET / nuget-publish-argparser-plugin (push) Blocked by required conditions
.NET / nuget-publish-argparser-attrs (push) Blocked by required conditions
.NET / nuget-publish-httpclient-plugin (push) Blocked by required conditions
.NET / nuget-publish-httpclient-attrs (push) Blocked by required conditions
.NET / nuget-publish-interfacemock-plugin (push) Blocked by required conditions
Import interface-mock generator (#14)
2024-10-07 22:10:30 +01:00

2.9 KiB

WoofWare.Whippet.Plugin.InterfaceMock

This is a Whippet plugin for defining mocks for interfaces.

It is a copy of the corresponding Myriad HttpClient plugin in WoofWare.Myriad, taken from commit d59ebdfccb87a06579fb99008a15f58ea8be394e.

Usage

Define a file like Client.fs:

type IPublicType =
    abstract Mem1 : string * int -> string list
    abstract Mem2 : string -> int

In your fsproj:

<Project>
    <ItemGroup>
        <Compile Include="Client.fs" />
        <Compile Include="GeneratedClient.fs">
            <WhippetFile>Client.fs</WhippetFile>
            <WhippetParamIPublicType>InterfaceMock</WhippetParamIPublicType>
        </Compile>
    </ItemGroup>

    <ItemGroup>
        <!-- Development dependencies, hence PrivateAssets="all". Note `WhippetPlugin="true"`. -->
        <PackageReference Include="WoofWare.Whippet.Plugin.InterfaceMock" WhippetPlugin="true" Version="" />
        <PackageReference Include="WoofWare.Whippet" Version="" PrivateAssets="all" />
    </ItemGroup>
</Project>

(This plugin follows a standard convention taken by WoofWare.Whippet.Plugin plugins, where you use Whippet parameters with the same name as each input type, whose contents are a !-delimited list of the generators which you wish to apply to that input type.)

The generator produces a type like this:

/// Mock record type for an interface
type internal PublicTypeMock =
    {
        Mem1 : string * int -> string list
        Mem2 : string -> int
    }

    static member Empty : PublicTypeMock =
        {
            Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
            Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
        }

    interface IPublicType with
        member this.Mem1 (arg0, arg1) = this.Mem1 (arg0, arg1)
        member this.Mem2 (arg0) = this.Mem2 (arg0)

What's the point?

Reflective mocking libraries like Foq in my experience are a rich source of flaky tests. The Grug-brained developer would prefer to do this without reflection, and this reduces the rate of strange one-in-ten-thousand "failed to generate IL" errors. But since F# does not let you partially update an interface definition, we instead stamp out a record, thereby allowing the programmer to use F#'s record-update syntax.

Features

You may supply an isInternal : bool argument:

<Compile Include="GeneratedClient.fs">
    <WhippetFile>Client.fs</WhippetFile>
    <WhippetParamIPublicType>InterfaceMock(false)</WhippetParamIPublicType>
</Compile>

By default, we make the resulting record type at most internal (never public), since this is intended only to be used in tests; but you can instead make it public by setting the false boolean.