FunkyMock 1.1.0

dotnet add package FunkyMock --version 1.1.0
                    
NuGet\Install-Package FunkyMock -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FunkyMock" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FunkyMock" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="FunkyMock" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FunkyMock --version 1.1.0
                    
#r "nuget: FunkyMock, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FunkyMock@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FunkyMock&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=FunkyMock&version=1.1.0
                    
Install as a Cake Tool

Overview

Testing with mocks should be used sparingly but when you do use this technique it should be easy to do right and obvious how it works. If there is a problem with the test or the production code the Mocking system should help, not hinder.

Function based mocks

At it's core I prefer to mock any functions using replacement functions with very simple test specific behaviour ideally just returning simple values.

// a test
[Test]
public void An_error_is_returned_for_out_of_stock_items() {
    var mock = new MockClient {
        // any stock item is "out of stock"
        OnFindStockById = _ => new StockItem { InStock = false }
    };
    var id = Guid.NewGuid();
    var response = new Controller(mock).HandleAddItem(id);

    Assert.AreEqual("Out Of Stock", response.Error);
    // if you really want to see how many calls were made
    Assert.AreEqual(1, mock.Calls.FindStockById.Count);
    Assert.AreEqual(id, mock.Calls.FindStockById[0].id);
}

// code to invoke the generator for MockClient
internal partial class MockClient : IStockClient { }

// the production interface
public interface IStockClient {
    StockItem? FindStockById(Guid id);
}

// some class under test
public class Controller {
    private readonly IStockClient _client;
    public Controller(IStockClient client) => _client = client;

    public Response HandleAddItem(Guid skuId) {
        if (!_client.FindStockById(skuId)!.InStock) {
            return new Response{ Error = "Out Of Stock" };
        }
        return new Response();
    }
}

You can see we implement as much as we need for mock. The mocked implementation is a simple function (or several functions) that is easy to write, read and debug. The interface parameters are captured and easy to assert on or ignore as you see fit.

All this is achieved with some Source Code Generation. All you need to do is define a partial class that implements the interface you care about and add the [Funky] attribute. Source generation creates the implementation and adds the source to your code for you to inspect, debug or ignore.

[Funky]
partial class MockClient : IStockClient { }

You can use your part of the partial class to keep related static helper methods. e.g.

partial class MockClient : IStockClient {
    public static StockItem? ReturnInStockItem(Guid id) =>
        new StockItem {
            Display = "Beans",
            InStock = true
        };
}

public void Any_test() {
    var mock = new MockClient {
        OnFindStockById = MockClient.ReturnInStockItem
    };
    // ...
}

Install

dotnet add package FunkyMock

Configuration

To control configuration add a file .globalconfig to the root of your consuming test project.

Interfaces are, by default, implemented explicitly ITheInterface.TheMethod() vs implicitly public TheMethod(). to switch this to implicit set the following

funky.explicit_interfaces = false

Mock types

There are Mocks, Stubs, Fakes and Test Doubles to name but a few. Here I use the word Mock to cover all types. The proposed approach does not limit you in any way to prefer other terminology or more precise meaning.

Complex Mocking Frameworks

This is my opinion on the current style of mocking frameworks. Historically I would have hand coded what I can now auto generate as the extra typing time saved complex mock test debugging time later.

This is a farly typical current approach with other mocking frameworks. If you pass the wrong uuid or don't specify Any or use the wrong type the test will just fail. The code that looks like a lambda isn't, it's an expression so is not designed to be executed, its designed to be examined by the framework. The frameworks encourage dense code blocks of setup where it is difficult to identify what is actually being tested. Simple DSL type factories can be built but are difficult to get right.

var mock = new Mock<IStockClient>();
mock.Setup(x => x.FindStockById(It.IsAny<Guid>())).
    Returns(new StockItem()));

// run test

mock.Verify(x => x.FindStockById(specificUuid) );
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 326 4/22/2023
1.0.6 341 3/25/2023
1.0.5 336 3/25/2023
1.0.4-preview1 265 3/20/2023