FunctionalDev.MoqHelpers 3.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package FunctionalDev.MoqHelpers --version 3.0.1                
NuGet\Install-Package FunctionalDev.MoqHelpers -Version 3.0.1                
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="FunctionalDev.MoqHelpers" Version="3.0.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add FunctionalDev.MoqHelpers --version 3.0.1                
#r "nuget: FunctionalDev.MoqHelpers, 3.0.1"                
#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.
// Install FunctionalDev.MoqHelpers as a Cake Addin
#addin nuget:?package=FunctionalDev.MoqHelpers&version=3.0.1

// Install FunctionalDev.MoqHelpers as a Cake Tool
#tool nuget:?package=FunctionalDev.MoqHelpers&version=3.0.1                

FunctionalDev.MoqHelpers

Moq Helpers is a library which has been created to aid unit testing.

Please read through the rest of this document to view tools which can help reduce the brittle nature of unit tests, decrease excess code when setting up objects and enhance the clarity of what it is you're testing.

A particular focus of this library is setting up Mock objects (including non-public members) and providing access to non-public members and methods of instantiated objects.

For a complete changelog, please see the ChangeLog below

Activators

Creating instances of objects is central to most unit testing. Activator classes have been provided to reduce the overhead and brittle nature of setting up classes, when it may not be required to provide all arguments for the constructor, if those arguments are not necessary for the particular unit test in focus.

These activators can be used to future-proof the creation of objects, in that future changes to an object constructor will not break existing unit tests, providing that the additional arguments do not affect existing behaviour.

Lazy Mock Activation

A MockActivator class can be used to create Mock{T} classes.

This activator has CreateLazy and CreateNull methods which allow the creation of Mock{T} classes without needing to provide all/any constructor arguments.

Whilst these methods can be used to provide all arguments, they can also be provided with some or all constructor arguments to enable future-proofing with future constructor signature changes.

Given the following class:

public abstract class Person
{
  protected Person(ILogger<Person> logger) { }
}

The following example uses CreateLazy, without arguments, to create a new instance of Mock{Person} without supplying an instance of ILogger{Person}. Note that the Person base type will be supplied new Mock{ILogger {Person}}.Object as a value for ILogger{Person}.

public Mock<Person> CreateLazy()
  => MockActivator.CreateLazy<Person>();

The following example uses CreateNull, without arguments, to create a new instance of Mock{Person} without supplying an instance of ILogger{Person}. Note that the Person base type will be supplied null as a value for ILogger{Person}.

public Mock<Person> CreateNull()
  => MockActivator.CreateNull<Person>();

The following example uses CreateLazy, with arguments, to create a new instance of Mock{Person} supplying an instance of Mock{ILogger{Person}}. Note that the Person base type will be supplied loggerMock.Object as a value for ILogger{Person}.

public Mock<Person> CreateLazyWithMockArgument(Mock<ILogger<Person>> loggerMock)
  => MockActivator.CreateLazy<Person>(loggerMock);

The following example uses CreateLazy, with arguments, to create a new instance of Mock{Person} supplying an instance of ILogger{Person}. Note that the Person base type will be supplied loggerInstance as a value for ILogger{Person}.

public Mock<Person> CreateLazyWithMockArgument(ILogger<Person> loggerInstance)
  => MockActivator.CreateLazy<Person>(loggerInstance);

Lazy Activation

A LazyActivator class can be used to create instances of concrete classes.

This activator has CreateLazy and CreateNull methods which allow the creation of concrete classes without needing to provide all/any constructor arguments.

Whilst these methods can be used to provide all arguments, they can also be provided with some or all constructor arguments to enable future-proofing with future constructor signature changes.

An example can be seen below:

public class Person
{
  public Person(ILogger<Person> logger) { }
}

The following example uses CreateLazy, without arguments, to create a new instance of Person without supplying an instance of ILogger{Person}. Note that the Person constructor will be supplied new Mock{ILogger{Person}}.Object as a value for ILogger{Person}.

public Person CreateLazy()
  => LazyActivator.CreateLazy<Person>();

The following example uses CreateNull, without arguments, to create a new instance of Person without supplying an instance of ILogger{Person}. Note that the Person constructor will be supplied null as a value for ILogger{Person}.

public Person CreateNull()
  => LazyActivator.CreateNull<Person>();

The following example uses CreateLazy, with arguments, to create a new instance of Person supplying an instance ofMock{ILogger{Person}}. Note that the Person constructor will be supplied loggerMock.Object as a value for ILogger{Person}.

public Person CreateLazyWithMockArgument(Mock<ILogger<Person>> loggerMock)
  => LazyActivator.CreateLazy<Person>(loggerMock);

The following example uses CreateLazy, with arguments, to create a new instance of Person supplying an instance of ILogger{Person}. Note that the Person constructor will be supplied loggerInstance as a value for ILogger{Person}.

public Person CreateLazyWithMockArgument(ILogger<Person> loggerInstance)
  => LazyActivator.CreateLazy<Person>(loggerInstance);

Container Creation

Container Lazy and Mock activation has been added to provide a means of extracted auto loaded construction objects. Please note that whilst this example uses MockActivator this is also present in LazyActivator.

private readonly TestClass _testClass;
private readonly Mock<ILogger<TestClass>> _loggerMock;
private readonly Mock<IWidget> _widgetMock;
private readonly IWidget _widget;

public UnitTest()
{
    var container = MockActivator.CreateLazyContainer<TestClass>();

    _testClass = container.Instance.Object;
    _loggerMock = container.GetArgumentAsMock<ILogger<TestClass>>();
    _widgetMock = container.GetArgumentAsMock<IWidget>();
    _widget = container.GetArgument<IWidget>();
}

Object Proxy

The ObjectProxy class can be used to interact (set/get) private members of a given instance, and can be used to invoke protected/private methods.

Dot separated member access is supported to access members of members recursively. E.g. ObjectProxy.For(person)["Name"."FirstName"]

Static classes can be setup with ObjectProxy.ForStatic to interact with static classes.

Examples

Given the following class:

public class Person
{
  private string Name { set; get; }

  private Person(ILogger<Person> logger, string name)
  {
    Name = name;
  }

  private string GetName()
    => Name;

  private string ToStringFor<T>(T arg)
    => arg.ToString();
}

Creating an instance of Person can be achieved as shown below.

var person = LazyActivator.CreateLazy<Person>("Fred");
// or
var person = LazyActivator.CreateLazy<Person>();

And members can be managed as shown below.

// Get.
var name = ObjectProxy.For(person)["Name"];

// Set.
ObjectProxy.For(person)["Name"] = "Bob";

Methods can be invoked via the proxy with:

var result = person.InvokeMethod<string>("GetName");

For full examples please see end of this file.

Setup Extension Methods

Several extension methods have been provided which enable functional Moq creation (returning Mock{T} to enable chaining) and to setup private/protected members.

Please note that to resovle the extension methods, it may be required to add the following using statement:

using FunctionalDev.MoqHelpers;

Please also note that the extension methods are not the typical .Setup(...).Returns(...) format. For simple method setups the format this library provides is:

.Setup(type => type.MethodName, Expression<Func<InputArgs, ReturnArg>>);

Given an example interface:

public interface IPerson
{
    string SetName(string name);
    int GetAge();
}

The following can be used to set up an IPerson interface.

var person = new Mock<IPerson>()
    .Setup(x => x.SetName, (string name) => "")
    .Setup(x => x.GetAge, () => 25);

Please note that this does not provide any filters on the arguments for methods (such as It.Is<T>(Func<T, bool>) filtering).

Full Example

public abstract record FullExampleObjectBase(ILogger<FullExampleObjectBase> Logger)
{
    public ILogger<FullExampleObjectBase> Logger { get; } = Logger;

    private int _localIntegerValue = 1;

    protected virtual string GetName()
        => nameof(FullExampleObjectBase);

    private void SetLocalIntegerValue(int newValue)
    {
        _localIntegerValue = newValue;
    }

    private int GetLocalIntegerValue()
        => _localIntegerValue;

    private int GenericMethod<T>() => 1;

    public abstract string GetNameAbstract(string arg);
}

public record FullExampleObject(ILogger<FullExampleObjectBase> Logger)
    : FullExampleObjectBase(Logger)
{
    public override string GetNameAbstract(string arg)
        => arg;
}

public static class FullExampleStaticClass
{
    private static void SetStaticValue(string arg) { }
}

public class FullExample
{
    public FullExampleObject CreateFullExampleObject()
        => LazyActivator.CreateLazy<FullExampleObject>();

    public FullExampleObject CreateFullExampleObjectWithArgument()
        => LazyActivator.CreateLazy<FullExampleObject>(Mock.Of<ILogger<FullExampleObjectBase>>());

    public void SetPrivate()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        proxy["_localIntegerValue"] = 5;
    }

    public void GetPrivate()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        var value = (int)proxy["_localIntegerValue"];
    }

    private void CallPrivate()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        proxy.InvokeMethod("SetLocalIntegerValue", 5);
    }

    private void CallPrivateWithReturning()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        int resultAsInt = proxy.InvokeMethod<int>("GetLocalIntegerValue");
        object resultAsObject = proxy.InvokeMethod("GetLocalIntegerValue");
    }

    private void CallGenericPrivateMethod()
    {
        var proxy = ObjectProxy.For(CreateFullExampleObject());
        int resultAsInt = proxy.InvokeGenericMethod<int>("GenericMethod", new[] { typeof(RecordClass) }, Array.Empty<object>());
        object resultAsObject = proxy.InvokeGenericMethod("GenericMethod", new[] { typeof(RecordClass) }, Array.Empty<object>());
    }

    private void ObjectProxyForStaticClass()
    {
        var proxy = ObjectProxy.ForStatic(typeof(FullExampleStaticClass));
        proxy.InvokeMethod("SetStaticValue", "Hello world");
    }

    private void SetupExtensionMethods()
    {
        MockActivator.CreateLazy<FullExampleObjectBase>()
            // Setting members.
            .Setup(x => x.Logger, Mock.Of<ILogger<FullExampleObjectBase>>())
            // Setting public methods.
            .Setup(x => x.GetNameAbstract, (string arg) => arg + "test")
            // Setting private methods.
            .Setup("GetName", () => "Hello World")
            // Also works for public methods.
            .Setup("GetNameAbstract", (string arg) => arg + "test")
            ;
    }
}

Chaining Calls on ActivatedObject/ActivatedObject{T}

Further changes in containers allows for chaining setups:

var container = LazyActivator.CreateLazyContainer<MyTestClass>()
  .SetupMock<IWidget>(widget => { })
  .SetupMock<ITestObject>(testObject => testObject.Setup(x => x.Name, "Fred"))
  .SetupMock<ITestObject>(testObject => testObject.Setup(x => x.Name, "Frank"))
  .Arguments
      .AddToArgumentCollection(new Mock<ILogger<MyTestClass>>().Object)
      .Parent
  .SetupMock<ILogger<MyTestClass>>(logger =>
  {
      logger.Setup(...)
  });

ChangeLog

3.0.1 | 11/01/2023

  • Introduced ChangeLog

3.0.0 | 11/01/2023

  • Breaking - ActivatedObject{T} no longer inherits from ActivatedObject, instead a new base class has been introduced (ActivatedObjectBase) which they both share.
  • Breaking - The two methods GetArgument and GetArgumentAsMock have been marked as obsolete (and have been moved to ActivatedObjectBase) and may be removed on the next major release.
  • Breaking - ActivatedObject no longer has a property called InstanceUnTyped it has been renamed to Instance with a type of object, mirroring the property on ActivatedObject{T} named Instance of type T (class generic type).
  • Added chaining calls to ActivatedObjects to enable setting up of mocks inline/chained.
  • Rewrite on the argument logic allowing custom injection of objects after instantiation.
  • Updated all ILogger{T} extension methods to also support ILogger.
  • Logger/Logger{T}.Unwrap() now returns the exception, if provided on invocations.
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. 
.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
3.2.0 164 9/17/2024
3.1.6 79 7/24/2024
3.1.5 126 7/8/2024
3.1.4 174 2/20/2024
3.1.3 152 1/22/2024
3.1.2 97 1/22/2024
3.1.1 107 1/21/2024
3.1.0 95 1/21/2024
3.0.25 221 12/6/2023
3.0.24 142 12/4/2023
3.0.23 187 11/15/2023
3.0.22 205 10/10/2023
3.0.21 140 10/6/2023
3.0.20 202 8/7/2023
3.0.19 157 7/31/2023
3.0.18 144 7/31/2023
3.0.17 174 6/27/2023
3.0.16 192 6/2/2023
3.0.15 148 6/2/2023
3.0.14 179 5/15/2023
3.0.13 259 4/18/2023
3.0.12 187 4/18/2023
3.0.11 180 4/17/2023
3.0.10 206 4/14/2023
3.0.9 188 4/14/2023
3.0.8 198 4/14/2023
3.0.7 192 4/14/2023
3.0.6 295 2/17/2023
3.0.5 240 2/16/2023
3.0.4 266 2/9/2023
3.0.3 268 2/7/2023
3.0.2 1,882 1/24/2023
3.0.1 360 1/11/2023
3.0.0 309 1/11/2023
2.1.4 488 11/1/2022
2.1.3 556 7/19/2022
2.1.2 495 5/3/2022
2.1.1 451 5/3/2022
2.1.0 443 4/27/2022
2.0.15 433 4/26/2022
2.0.14 494 3/10/2022
2.0.13 748 1/27/2022
2.0.12 662 1/24/2022
2.0.11 653 1/21/2022
2.0.10 427 9/10/2021
2.0.9 351 9/10/2021
2.0.8 312 9/3/2021
2.0.7 312 9/3/2021
2.0.6 337 9/2/2021
2.0.5 311 8/27/2021
2.0.4 293 8/27/2021
2.0.3 307 8/13/2021
2.0.2 356 7/29/2021
2.0.1 339 7/28/2021
2.0.0 336 7/28/2021
1.1.0 389 7/28/2021
1.0.0 376 7/27/2021