Defining mocked response based on early void method calls to the same mocked object

Viewed 45

Here's the class that I want to mock, massively truncated.

public class FooHandler
{
    private FooInstance foo;

    public void ConstructInstance(string fooSpecs)
    {
        foo = SomeMethod(fooSpecs);
    }

    public string GetSomeProperty()
    {
        return foo.SomeProperty();
    }
}

In reality every method has a lot of internal logic. This is just the idea.

The code that I want to test requires a FooHandler, and it calls both methods, one after the other.

I want to mock my FooHandler to test whether my code calls it correctly.

var mockedFooHandler = new Mock<FooHandler>();
mockedFooHandler.Setup(x => x.ConstructInstance(EXAMPLE_FOO_SPEC));
mockedFooHandler.Setup(x => x.GetSomeProperty()).Returns(EXPECTED_PROPERTY);

Now, I want to tie these two setups together. I want to return different EXPECTED_PROPERTY's if the first method has been called with a different EXAMPLE_FOO_SPEC. The first method is void, and the second one takes no parameters. So I kind of want to set the internal state of the mocked object, the way the real one does. But the state is private.

Making several different mockedFooHandlers with their own programmed responses, to inject at different places, is not possible.

How should I go about that?

2 Answers

For void methods, can use Verify to validate that a method has been called in a certain way. example:

_mockedInstance.Verify(x => x.Method(expectedParameter), Times.Once);

If the method isn't called as expected, the verify will throw an exception thus failing the test. This should be called at the end of the test where you would normally assert your results.

https://github.com/Moq/moq4/wiki/Quickstart#verification

If I understand correctly, you want your mock to be able to give values back from one method, according to what a previous method receives from the class under test.

The following does that by capturing the parameter passed to the first mocked method, and the using that captured parameter as the input for a lambda function within the Returns method of the second mocked method.

public class UnitTest1
{
    [TestMethod]
    public void Can_Capture_Parameters()
    {
        var mockedFooHandler = new Mock<IFooHandler>();

        string response = "";

        // Here we use the moq Callback function to capture the paramter
        mockedFooHandler.Setup(x => x.ConstructInstance(It.IsAny<string>()))
            .Callback<string>(r => response = r);
        mockedFooHandler.Setup(x => x.GetSomeProperty()).Returns("b");

        mockedFooHandler.Object.ConstructInstance("testing");

        Assert.AreEqual("testing", response);
    }

    [TestMethod]
    public void Can_React_To_Parameters()
    {
        var mockedFooHandler = new Mock<IFooHandler>();

        string response = "";

        var responseDictionary = new Dictionary<string, string>()
        {
            {"first", "first response"},
            {"second", "second response"},
            {"third", "third response"}
        };

        mockedFooHandler.Setup(x => x.ConstructInstance(It.IsAny<string>()))
            .Callback<string>(r => response = r);

        // And here we use that captured parameter in a lambda. In this case using a dictionary to determine the desired response
        mockedFooHandler.Setup(x => x.GetSomeProperty()).Returns(() => responseDictionary[response]);

        mockedFooHandler.Object.ConstructInstance("first");

        Assert.AreEqual("first response", mockedFooHandler.Object.GetSomeProperty());
    }
    }

    public interface IFooHandler
    {
        void ConstructInstance(string fooSpecs);
        string GetSomeProperty();
    }

    public class FooHandler : IFooHandler
    {
        public void ConstructInstance(string fooSpecs)
        {
        }

        public string GetSomeProperty()
        {
            return "unexpected data";
        }
    }

I however agree with Kieran Devlin's comment, that the need to do this is almost certainly a code smell, and points to something wrong with your design.

Related