简体   繁体   中英

Moq an object created inside the method being tested

In the following example, I want to test the TestMe.DoSomething() function.

I want to mock the ISomething interface that is used within this method and make it return different values (depending on the specific unit test.)

In real life the ISomething interface winds up calling out to expensive 3rd party resources -- I definitely don't want to just call a real ISomething .

Here is the example structure:

class TestMe
{
    public void DoSomething()
    {
        ISomething s = SomethingFactory();
        int i = s.Run();

        //do things with i that I want to test
    }

    private ISomething SomethingFactory()
    {
        return new Something();
    }
}

interface ISomething
{
    int Run();
}

class Something : ISomething
{
    public int Run()
    {
        return 1;
    }
}

Here is code that doesn't work:

        var fakeSomething = new Mock<ISomething>();
        var testMe = new TestMe();
        Mock.Get(testMe).Setup(p => p.SomethingFactory()).Returns(fakeSomething.Object);
        testMe.DoSomething();

Because SomethingFactory() is private , I cannot set the return value from that method to be what I want.

Any advice on how I can solve this?

Make the factory a full interface / class and remove the SomethingFactory method from TestMe.

public interface ISomethingFactory {
  ISomething MakeSomething();
}

public sealed class SomethingFactory {
  public ISomething MakeSomething() {
    return new Something();
  }
}

class TestMe
{
    private readonly ISomethingFactory _somethingFactory;

    public TestMe(ISomethingFactory somethingFactory) {
      _somethingFactory = somethingFactory;
    }

    public void DoSomething()
    {
        ISomething s = _somethingFactory.MakeSomething();
        int i = s.Run();

        //do things with i that I want to test
    }
}

This will allow you to mock ISomethingFactory to return a mock of ISomething.

While I think you may protest this solution as too drastic a change, I think its better than making a class that's not sealed with a members who's only reason for being virtual is for testing.

  1. You can inject your dependency. If you don't want to break all your callers you can add two constructors and use the one that lets you inject fake in tests

     class TestMe { private readonly ISomething something; TestMe() : this(new RealSomething() { } TestMe(ISomething sth) { something = sth; } public void DoSomething() { ISomething s = SomethingFactory(); int i = s.Run(); //do things with i that I want to test } private ISomething SomethingFactory() { return new Something(); } } 
  2. Second way would be to change the

     SomethingFactory 

    method to protected virtual and override it in derived class and use that class instead, or to setup

     class TestableTestMe : TestMe { private readonly ISomething something; TestableTestMe(ISomething testSpecific) { something = testSpecific; } public void DoSomething() { ISomething s = SomethingFactory(); int i = s.Run(); //do things with i that I want to test } protected override ISomething SomethingFactory() { return something; } } 

This technique is called "extract and override"

Changing SomethingFactory() to be protected virtual allows you to use Moq.Protected to access the method by its name:

public class TestMe 
{
    public void DoSomething()
    {
        ISomething s = SomethingFactory();
        int i = s.Run();

        //do things with i that I want to test
    }

    protected virtual ISomething SomethingFactory()
    {
        return new Something();
    }
}

public interface ISomething
{
    int Run();
}

public class Something : ISomething
{
    public int Run()
    {
        return 1;
    }
}

So you can run this test:

        var fakeSomething = new Mock<ISomething>();
        fakeSomething.Setup(p => p.Run()).Returns(2);
        var testMe = new Mock<TestMe>();
        testMe.Protected().Setup<ISomething>("SomethingFactory").Returns(fakeSomething.Object);
        testMe.Object.DoSomething();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM