13

I have been using Moq for my mocking needs the last years, but after looking at FakeItEasy i wanted to give it a try.

I often want to test that a method have been called with the correct parameters, but i found no satisfactory way to do this with FakeItEasy.

I have the following code to test:

    public class WizardStateEngine : IWizardStateEngine
{
    private readonly IWorkflowInvoker _workflowInvoker;
    private List<CustomBookmark> _history;

    public WizardStateEngine(IWorkflowInvoker workflowInvoker)
    {
        _workflowInvoker = workflowInvoker;
    }

    public void Initialize(List<CustomBookmark> history)
    {
        _history = history;
    }

    public WizardStateContext Execute(Command command, WizardStateContext stateContext, CustomBookmark step)
    {
        Activity workflow = new MyActivity();
        var input = new Dictionary<string, object>();
        input["Action"] = command;
        input["Context"] = stateContext;
        input["BookmarkHistory"] = _history;

        var output = _workflowInvoker.Invoke(workflow, input);

        _history = output["BookmarkHistory"] as List<CustomBookmark>;

        return output["Context"] as WizardStateContext;
    }

    public List<CustomBookmark> GetBookmarkHistory()
    {
        return _history;
    }
}

I want to write some tests that verifies the input to _workflowInvoker.Invoke(). My TestInitialize method sets up the needed resources and save the input dictionary to _workflowInvoker.Invoke() as a local field _wfInput.

    [TestInitialize]
    public void TestInitialize()
    {
        _wizardStateContext = new WizardStateContext();
        _workflowInvoker = A.Fake<IWorkflowInvoker>();
        _wizardStateEngine = new WizardStateEngine(_workflowInvoker);

        _outputContext = new WizardStateContext();
        _outputHistory = new List<CustomBookmark>();
        _wfOutput = new Dictionary<string, object>
                        {{"Context", _outputContext}, {"BookmarkHistory", _outputHistory}};

        _history = new List<CustomBookmark>();

        A.CallTo(() =>
                 _workflowInvoker.Invoke(A<Activity>.Ignored, A<Dictionary<string, object>>.Ignored))
            .Invokes(x => _wfInput = x.Arguments.Get<Dictionary<string, object>>("input"))
            .Returns(_wfOutput);

        _wizardStateEngine.Initialize(_history);
    }

After the setup i have multiple tests like this:

    [TestMethod]
    public void Should_invoke_with_correct_command()
    {
        _wizardStateEngine.Execute(Command.Start, null, null);

        ((Command) _wfInput["Action"]).ShouldEqual(Command.Start);
    }

    [TestMethod]
    public void Should_invoke_with_correct_context()
    {
        _wizardStateEngine.Execute(Command.Start, _wizardStateContext, null);

        ((WizardStateContext) _wfInput["Context"]).ShouldEqual(_wizardStateContext);
    }

    [TestMethod]
    public void Should_invoke_with_correct_history()
    {
        _wizardStateEngine.Execute(Command.Start, _wizardStateContext, null);

        ((List<CustomBookmark>) _wfInput["BookmarkHistory"]).ShouldEqual(_history);
    }

I do not like the magic string "input" in the TestInitialize for getting the passed argument (or magic number). I can write the tests without the local field like this:

    [TestMethod]
    public void Should_invoke_with_correct_context()
    {
        _wizardStateEngine.Execute(Command.Start, _wizardStateContext, null);

        A.CallTo(() =>
                 _workflowInvoker.Invoke(A<Activity>._,
                                         A<Dictionary<string, object>>.That.Matches(
                                             x => (WizardStateContext) x["Context"] == _wizardStateContext)))
            .MustHaveHappened();
    }

But i find the tests with the local field more readable.

Are there any way to setup saving of the input as a field i my test class without magic numbers or strings?

I hope the updated example in the question shows why i would like to use the local field. I am more than willing to write my tests without the local field if i can find a nice readable way to do it.

Olsenius
  • 313
  • 1
  • 2
  • 7
  • Regarding Darin Dimitrov and Patrik Hägne answers: If your unit test structure uses a distinct test fixture per method being tested (as Phil Haack shows in [this](http://haacked.com/archive/2012/01/02/structuring-unit-tests.aspx) post), it makes perfect sense to put setup logic into a setup method. In order to test many distinct facts over a single method, you'll use basically the same setup, with tiny changes for each fact being tested. See also Brian Rigsby approach [here](http://zendeveloper.blogspot.com.br/2012/01/structuring-unit-tests.html). – rsenna Aug 06 '12 at 20:16

2 Answers2

25
A.CallTo(() => service.DoSomething(A<int>.That.Matches(x => x == 100)))
 .MustHaveHappened();
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 3
    Thanks for the answer, but it is not quite what im looking for. I would like to save the input as a field in my test class. This way i can set it up once in SetUp, and then use the local variable in multiple tests. With complex objects the expressions can be quite hard to read. Ill update my question.. – Olsenius Aug 20 '11 at 17:14
  • 1
    @Olsenius, why would you need to do that? You could perform the necessary assertions inside the lambda. You no longer need to call `inputNumber.ShouldEqual(100);`. – Darin Dimitrov Aug 20 '11 at 17:16
  • i updated my question. I want to save the input as a field for multiple tests, and do setup only once. – Olsenius Aug 20 '11 at 17:27
  • 5
    @Olsenius, what you are trying to do is bad practice IMHO. Unit tests should be independent one from another and you should not have one test initialize data that should be used by another test. On the other hand you could write a function which will be invoked in the `A.That.Matches` constraint on each test to verify the data. – Darin Dimitrov Aug 20 '11 at 17:49
  • the tests are independent. I updated my question to show the exact class im trying to test. I might be testing wrong, but i find the tests with a local field for the input easier to read. – Olsenius Aug 22 '11 at 07:35
  • 11
    Actually there's no reason to use the argument constraint "A.That.Matches(x => x == 100)" in this case. Just use "A.CallTo(() => service.DoSomething(100)) .MustHaveHappened();" – Patrik Hägne Aug 24 '11 at 19:39
  • Ultimately the important thing to understand is that FIE is testing things 'inline'. It intercepts the call, applies the assertions you provided, and then continues to the original call. The only way FIE could 'return' the parameter to your tests in a meaningful way is for it to make a deep copy at that point (in case it is altered later) store it somewhere, and retrieve it after all execution is complete. It's perhaps possible... but ugly. – Mir Feb 13 '13 at 19:48
  • I did it with an object `A.CallTo(() => _service.DoSomething(A.That.Matches(x => x.Property == null))).MustHaveHappenedOnceExactly();` – Alina Striltsova Sep 20 '22 at 11:27
19

I agree with everything Darin says, it seems like a bad practice to do what you're doing. You say that it looks "stupid" in this trivial example, could you provide an example where it looks smart?

Anyhow, the following test would have exactly the same behaviour as the Moq-test:

[Test]
public void Should_do_something_with_correct_input()
{
    int inputNumber = 0;

    var service = A.Fake<IService>();
    A.CallTo(() => service.DoSomething(A<int>._))
        .Invokes((int x) => inputNumber = x);

    var system = new System(service);
    system.InvokeService();

    inputNumber.ShouldEqual(100);
}
Patrik Hägne
  • 16,751
  • 5
  • 52
  • 60
  • I updated my question with the actual class I'm trying to tests. I would also prefer to not use magic numbers. – Olsenius Aug 22 '11 at 07:37
  • 4
    The Moq implementation is exactly equivalent, it's just as magic. It requires that the first argument is an int, just as FIE does. – Patrik Hägne Aug 22 '11 at 10:47