Mario
Mario

Reputation: 3445

In unit test when using nSubstitute and Autofixture as a DI container, how to get mocked object?

I used to use Moq and AutoMoqer in unit tests, but my team has decided to change to NSubstitute. We use DI heavily, so I'd like to be able to ask for a target to test and have that target automatically given all mocked objects to its constructor, or in other words a DI container that passes in mocks. I also want to modify those mocked objects as necessary.

Example using Moq/AutoMoq/MSTest

[TestMethod]
public void ReturnSomeMethod_WithDependenciesInjectedAndD1Configured_ReturnsConfiguredValue()
{
    const int expected = 3;
    var diContainer = new AutoMoq.AutoMoqer();

    var mockedObj = diContainer.GetMock<IDependency1>();
    mockedObj
        .Setup(mock => mock.SomeMethod())
        .Returns(expected);

    var target = diContainer.Resolve<MyClass>();
    int actual = target.ReturnSomeMethod();

    Assert.AreEqual(actual, expected);
}

public interface IDependency1
{
    int SomeMethod();
}

public interface IDependency2
{
    int NotUsedInOurExample();
}

public class MyClass
{
    private readonly IDependency1 _d1;
    private readonly IDependency2 _d2;

    //please imagine this has a bunch of dependencies, not just two
    public MyClass(IDependency1 d1, IDependency2 d2)
    {
        _d1 = d1;
        _d2 = d2;
    }

    public int ReturnSomeMethod()
    {
        return _d1.SomeMethod();
    }
}

Since my question was poorly worded and I have researched a bit more, I have found a way to get this working using NSubstitute/AutofacContrib.NSubstitute/XUnit:

[Fact]
public void ReturnSomeMethod_WithDependenciesInjectedAndD1Configured_ReturnsConfiguredValue()
{
    const int expected = 3;
    var autoSubstitute = new AutoSubstitute();
    autoSubstitute.Resolve<IDependency1>().SomeMethod().Returns(expected);

    var target = autoSubstitute.Resolve<MyClass>();
    int actual = target.ReturnSomeMethod();

    Assert.Equal(actual, expected);
}

public interface IDependency1
{
    int SomeMethod();
}

public interface IDependency2
{
    int NotUsedInOurExample();
}

public class MyClass
{
    private readonly IDependency1 _d1;
    private readonly IDependency2 _d2;

    //please imagine this has a bunch of dependencies, not just two
    public MyClass(IDependency1 d1, IDependency2 d2)
    {
        _d1 = d1;
        _d2 = d2;
    }

    public int ReturnSomeMethod()
    {
        return _d1.SomeMethod();
    }
}

I still have my original question. How can I do this using AutoFixture.AutoNSubstitute as a DI container?

Upvotes: 3

Views: 3360

Answers (2)

Mark Seemann
Mark Seemann

Reputation: 233277

You can turn AutoFixture into an Auto-Mocking Container with various dynamic mock libraries, including NSubstitute.

Rewriting the OP test is as easy as this:

[Fact]
public void ReturnSomeMethod_UsingAutoFixtureAutoNSubstitute()
{
    const int expected = 3;
    var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
    fixture.Freeze<IDependency1>().SomeMethod().Returns(expected);

    var target = fixture.Create<MyClass>();
    var actual = target.ReturnSomeMethod();

    Assert.Equal(actual, expected);
}

Here, I've used xUnit.net instead of MSTest, but it ought to be trivial to translate it.

The key here is the Freeze method, which essentially changes the lifetime of the type argument from transient to singleton. This means that after Freeze has been called, all other times that particular Fixture instance has to create an IDependency object, it'll reuse the object it froze.

Freeze also returns the object it just froze, which enables you to easily keep 'dotting' into it - in this case by using NSubstitute's API.

Upvotes: 6

Fabio
Fabio

Reputation: 32445

I little bid confused when you call Autofixture a Dependency Injection container. It is not.

AutoFixture is simply generate values/instances you don't care about in current test, but they cannot hold default values.

In your case, you correctly created mock of IPromise, but class under test doesn't know about it. AutoFixture have generated "own" instance and pass it to class under test.

You should create instance of class under test manually - because you need full control of all dependencies - for documentation reasons, where other developers can see how object created

You can use AutoFixture for dependencies you do not care about in the particular test, but for getting class under test work properly this dependency should return some value (not null or default values).

// Arrange
var fixture = New Fixture();

var input = fixture.Create<int>(); // Will generate some integer value
var expected = fixture.Create<string>();

var dummyDependency = fixture.Create<IPromiseOne>(); // Don't care about in this test
var requiredDependency = Substitute.For<IPromiseTwo>(); // Dependency required in the test

// return "expected" only when "input" given
requiredDependency.SomeFunction(input).Returns(expected); 

var classUnderTest = new ClassUnderTest(requiredDependency, dependencyTwo);

// Act
var actual = classUnderTest.Convert(input);

// Assert
actual.Should().Be(expected);

For multiple tests you can introduce some factory class which create instance of class under the test and expose used dependencies as public properties, so you have access to them in the test.
Or use dependencies as private members of test class, so all tests have access to them.

Of course AutoFixture provide possibility to configuration possibilities. So you can configure to always return "your" mock when some type is asked by fixture

var fixture = new Fixture();

var myMock = Substitute.For<IPromise>();
myMock.SomeFunction().Returns(default(ReturnClass)) // default value is null

// Configure fixture to always return your mock
fixture.Register<IPromise>(() => myMock);

var classUnderTest = fixture.Create<ClassUnderTest>();

// Execute test

However you should be careful when creating class under test with AutoFixture, because AutFixture will generate random values for all public properties, which can be undesired, because for testing you need full control of your class under test.

Upvotes: -1

Related Questions