Moq implementation memo

6 minute read

Moq implementation memo

Note that I used Moq in my C # test implementation.

environment

  • Use Visual Studio 2019 project template (NUnit test project (.NET Core))
  • Install Moq from NuGet package

Class implementation to be tested

It is an implementation for example only, and is not a test target for which Moq is effective.

Human.cs


public class Human
{
    /// <summary>
    ///Last name
    /// </summary>
    private string FamilyName { get; }

    /// <summary>
    ///name
    /// </summary>
    private string GivenName { get; }

    /// <summary>
    ///age
    /// </summary>
    public virtual int Age { get; }

    public Human(string familyName, string givenName, int age)
    {
        FamilyName = familyName;
        GivenName = givenName;
        Age = age;
    }

    /// <summary>
    ///Create a full name.
    /// </summary>
    /// <returns>full name</returns>
    protected virtual string CreateFullName()
    {
        return $"{FamilyName} {GivenName}";
    }

    /// <summary>
    ///Create a full name with age.
    /// </summary>
    /// <returns>Full name with age</returns>
    public virtual string CreateFullNameWithAge()
    {
        return $"{CreateFullName()} {Age}";
    }

    /// <summary>
    ///Create a full name with age and its units.
    /// </summary>
    /// <returns>Full name with age and its units</returns>
    public virtual string CreateFullNameWithAge(string ageUnit)
    {
        return $"{CreateFullNameWithAge()}{ageUnit}";
    }
}

important point

Points to note when implementing the test target class.

virual

The virtual qualifier is required because the method to be mocked must be in an overridable state. Visibility is also protected or better.

The following is an error statement when trying to mock a non-overridable method. (Temporarily remove the virtual qualifier from the CreateFullName () method and execute)

System.NotSupportedException : Unsupported expression: mock => mock.CreateFullName()
    Non-overridable members (here: Human.CreateFullName) may not be used in setup / verification expressions.

static

Static methods and properties cannot be mocked.

Test implementation

The structure of the class used in the test implementation item.

Tests.cs


public class Tests
{
    /// <summary>
    ///Last name(Fixed value for testing)
    /// </summary>
    private string FamilyName { get; set; }

    /// <summary>
    ///name(Fixed value for testing)
    /// </summary>
    private string GivenName { get; set; }

    /// <summary>
    ///age(Fixed value for testing)
    /// </summary>
    private int Age { get; set; }

    /// <summary>
    ///Common test settings.
    /// </summary>
    [SetUp]
    public void Setup()
    {
        FamilyName = "Last name";
        GivenName = "name";
        Age = 20;
    }

    //Describe the test implementation below
}

Basic

Mocking with Moq uses the Moq.Mock <T> class.

At the time of new, set the class to be mocked in the <T> part, and set the method of the constructor to be mocked in the argument.

Mocked objects are called from Moq # Object.

var humanMock = new Mock<Human>(FamilyName, GivenName, Age)
humanMock.Object.CreateFullNameWithAge();

CallBase = false

The first thing I stumbled upon in the Mock class was handling the CallBase property.

This value is initially set to false.

In the following example, CallBase is false, so the unmocked method will return null. (The method itself is not called)

CallBase_False


   [Test]
   public void CallBase_False()
   {
       var humanMock = new Mock<Human>(FamilyName, GivenName, Age);
       Assert.AreEqual(null, humanMock.Object.CreateFullNameWithAge());
   }

CallBase = true

In the following example, CallBase is true, so the original method will be called for the unmocked method.

CallBase_True


   [Test]
   public void CallBase_True()
   {
       var humanMock = new Mock<Human>(FamilyName, GivenName, Age) { CallBase = true };
       Assert.AreEqual($"{FamilyName} {GivenName} {Age}", humanMock.Object.CreateFullNameWithAge());
   }

Other

  • Moq # Object is the same class as the mock target. Not only can it be used directly in the test method, but it can also be used indirectly through arguments and fields.

Property mocking

Specify the property to be mocked from the Func type argument of the Mock # SetupGet method, and set the return value to be mocked from the Returns method of the return object.

Mocked properties will always return the set return value.

OverrideProperty_Age


   public void OverrideProperty_Age()
   {
       var humanMock = new Mock<Human>(FamilyName, GivenName, Age) { CallBase = true };
       humanMock.SetupGet(m => m.Age).Returns(9999);

       Assert.AreEqual($"{FamilyName} {GivenName} 9999", humanMock.Object.CreateFullNameWithAge());
       Assert.AreEqual($"{FamilyName} {GivenName}9999 years old", humanMock.Object.CreateFullNameWithAge("Talent"));
   }

Mocking public class methods

The argument of the mocked method of Setup (Func) is only type specification

Specify the method to be mocked from the Func type argument of the Mock # Setup method, and set the return value to be mocked from the Returns method of the return object.

Use the Moq.It <T> class for the method set by the Func type argument, and explicitly set the mocking method including the argument configuration.

OverrideMethod_CreateFullNameWithAgeUnit


    /// <summary>
    ///Override CreateFullNameWithAge with arguments.
    /// </summary>
    [Test]
    public void OverrideMethod_CreateFullNameWithAgeUnit()
    {
        var humanMock = new Mock<Human>(FamilyName, GivenName, Age) { CallBase = true };
        humanMock.Setup(m => m.CreateFullNameWithAge(It.IsAny<string>())).Returns("Overwrite");

        //The argument configuration specified in Setup is mocked
        //The original method is not called and the return value set in Returns is returned
        Assert.AreEqual("Overwrite", humanMock.Object.CreateFullNameWithAge("age"));

        //Same name as the method specified in Setup, but with a different argument structure
        //The original method is called because it is not subject to mocking
        Assert.AreEqual($"{FamilyName} {GivenName} {Age}", humanMock.Object.CreateFullNameWithAge());
    }

Fixed value for the argument of the mocked method of Setup (Func)

You can also set a fixed value without using Moq.It <T>.

OverrideMethod_CreateFullNameWithAgeUnit_2


    /// <summary>
    ///Override CreateFullNameWithAge with arguments.
    /// </summary>
    [Test]
    public void OverrideMethod_CreateFullNameWithAgeUnit_2()
    {
        var humanMock = new Mock<Human>(FamilyName, GivenName, Age) { CallBase = true };
        humanMock.Setup(m => m.CreateFullNameWithAge("age")).Returns("Overwrite");

        //The method name and arguments specified in Setup match
        //Due to mocking, the original method is not called and the return value set in Returns is returned.
        Assert.AreEqual("Overwrite", humanMock.Object.CreateFullNameWithAge("age"));

        //The argument configuration has the same name and type as the method specified in Setup, but the exact setting value is different.
        //The original method is called because it is not subject to mocking
        Assert.AreEqual($"{FamilyName} {GivenName} {Age}Talent", humanMock.Object.CreateFullNameWithAge("Talent"));
    }

Other notes

  • Asynchronous methods can use ReturnsAsync.

Mocking protected class methods

Mocking a method with no arguments

Set mocking from the Setup <T> method of the return object of the Moq # Protected method.

Set the return type in the <T> part, and set the method name to be mocked in the first argument.

Mock_ProtectedMethod


    /// <summary>
    ///Mocking protected methods.
    /// </summary>
    [Test]
    public void Mock_ProtectedMethod()
    {
        var humanMock = new Mock<Human>(FamilyName, GivenName, Age) { CallBase = true };
        humanMock.Protected().Setup<string>("CreateFullName").Returns("Overwrite");

        //The overwritten return value is returned
        Assert.AreEqual($"Overwrite{Age}", humanMock.Object.CreateFullNameWithAge());
    }

Mocking methods with arguments

Set the argument configuration using ʻItExpr.IsAny ()` etc. for the second and subsequent arguments of `Setup `.

MockProtectedMethod_WithArgs


humanMock.Protected().Setup<string>("GetAgeWithUnit", ItExpr.IsAny<string>()).Returns("Overwrite");

Method execution

I think it’s unusual for a normal test implementation to not always test a mocked method.

It also describes how to execute reflection for methods that are not in the range of Moq but whose visibility is not public.

Run_ProtectedMethod


    [Test]
    public void Run_ProtectedMethod()
    {
        var human = new Human(FamilyName, GivenName, Age);
        Type type = human.GetType();
        MethodInfo methodInfo = type.GetMethod("CreateFullName", BindingFlags.Instance | BindingFlags.NonPublic);

        //For methods with arguments, the second argument is object[]Pass a value of type
        Assert.AreEqual($"{FamilyName} {GivenName}", methodInfo.Invoke(human, null));
    }

Mocking interface methods

The above is the description of the class method.

I felt uncomfortable about making the method overridable just for testing, but @ naminodarie’s comment showed that the interface could be used to avoid overriding.

IHuman.cs


/// <summary>
///The interface of the class under test.
/// </summary>
public interface IHuman
{
    /// <summary>
    ///Create a full name with age.
    /// </summary>
    /// <returns>Full name with age</returns>
    string CreateFullNameWithAge();

    /// <summary>
    ///Create a full name with age and its units.
    /// </summary>
    /// <returns>Full name with age and its units</returns>
    string CreateFullNameWithAge(string ageUnit);
}

Mock_Interface


    [Test]
    public void Mock_Interface()
    {
        var humanMock = new Mock<IHuman>();

        //Mocking settings are the same as based on the class
        humanMock.Setup(m => m.CreateFullNameWithAge(It.IsAny<string>())).Returns("Overwrite");

        //Since it is the same as the argument setting specified in Setup, it is not called and the return value set in Returns is returned.
        Assert.AreEqual("Overwrite", humanMock.Object.CreateFullNameWithAge("age"));
    }

Premonition that it seems to be particularly effective when temporarily implementing or using fake objects in a specific environment.

Other notes

However, it cannot be used for the following purposes.

  • Mocking a method called from a method.
  • Mock protected methods.

Miscellaneous feelings

  • Convenient.
  • Is the class actually used for mocking Mock?
  • What is the appropriate way to test around the process using static methods? Should static methods be used in a dedicated method instead of being described in the middle of the method?