6

Is it acceptable to do asserts in your callbacks if you later verify that the methods were called? Is this the preferred way of making sure my mock is getting the expected parameters passed to it, or should I set a local variable in my callback and do the asserts on that instance?

I have a situation where I have some logic in a Presenter class that derives values based on inputs and passes them to a Creator class. To test the logic in the Presenter class I want to verify that the proper derived values are observed when the Creator is called. I came up with the example below that works, but I'm not sure if I like this approach:

[TestFixture]
public class WidgetCreatorPresenterTester
{
    [Test]
    public void Properly_Generates_DerivedName()
    {
        var widgetCreator = new Mock<IWidgetCreator>();
        widgetCreator.Setup(a => a.Create(It.IsAny<Widget>()))
                     .Callback((Widget widget) => 
                     Assert.AreEqual("Derived.Name", widget.DerivedName));

        var presenter = new WidgetCreatorPresenter(widgetCreator.Object);
        presenter.Save("Name");

        widgetCreator.Verify(a => a.Create(It.IsAny<Widget>()), Times.Once());
    }
}

I'm concerned because without the Verify call at the end, there is no guarantee that the assert in the callback would be invoked. Another approach would be to set a local variable in the callback:

[Test]
public void Properly_Generates_DerivedName()
{
    var widgetCreator = new Mock<IWidgetCreator>();
    Widget localWidget = null;
    widgetCreator.Setup(a => a.Create(It.IsAny<Widget>()))
        .Callback((Widget widget) => localWidget = widget);

    var presenter = new WidgetCreatorPresenter(widgetCreator.Object);
    presenter.Save("Name");

    widgetCreator.Verify(a => a.Create(It.IsAny<Widget>()), Times.Once());
    Assert.IsNotNull(localWidget);
    Assert.AreEqual("Derived.Name", localWidget.DerivedName);
}

I feel that this approach is less error prone since it is more explicit, and it's easier to see that the Assert statements will be called. Is one approach preferable to the other? Is there a simpler way to test the input parameter passed to a mock that I'm missing?

In case it is helpful, here is the rest of the code for this example:

public class Widget
{
    public string Name { get; set; }
    public string DerivedName { get; set; }
}

public class WidgetCreatorPresenter
{
    private readonly IWidgetCreator _creator;

    public WidgetCreatorPresenter(IWidgetCreator creator)
    {
        _creator = creator;
    }

    public void Save(string name)
    {
        _creator.Create(
            new Widget { Name = name, DerivedName = GetDerivedName(name) });
    }

    //This is the method I want to test
    private static string GetDerivedName(string name)
    {
        return string.Format("Derived.{0}", name);
    }
}

public interface IWidgetCreator
{
    void Create(Widget widget);
}

EDIT
I updated the code to make the second approach I outlined in the question easier to use. I pulled creation of the expression used in Setup/Verify into a separate variable so I only have to define it once. I feel like this method is what I'm most comfortable with, it's easy to setup and fails with good error messages.

[Test]
public void Properly_Generates_DerivedName()
{
    var widgetCreator = new Mock<IWidgetCreator>();
    Widget localWidget = null;

    Expression<Action<IWidgetCreator>> expressionCreate = 
        (w => w.Create(It.IsAny<Widget>()));
    widgetCreator.Setup(expressionCreate)
        .Callback((Widget widget) => localWidget = widget);

    var presenter = new WidgetCreatorPresenter(widgetCreator.Object);
    presenter.Save("Name");

    widgetCreator.Verify(expressionCreate, Times.Once());
    Assert.IsNotNull(localWidget);
    Assert.AreEqual("Derived.Name", localWidget.DerivedName);
}
rsbarro
  • 27,021
  • 9
  • 71
  • 75

4 Answers4

5

What I do is do the Verify with matches in keeping with AAA. And becuase of this the Setup is not required. You can inline it but I separated it out to make it look cleaner.

[Test]
public void Properly_Generates_DerivedName()
{
    var widgetCreator = new Mock<IWidgetCreator>();

    var presenter = new WidgetCreatorPresenter(widgetCreator.Object);
    presenter.Save("Name");

    widgetCreator.Verify(a => a.Create(MatchesWidget("Derived.Name"));
}

private Widget MatchesWidget(string derivedName)
{
    return It.Is<Widget>(m => m.DerivedName == derivedName);
}
aqwert
  • 10,559
  • 2
  • 41
  • 61
  • +1 Thanks for the answer. It's much easier to read than my examples, but the fail messages are a bit weird for a unit test. Instead of "expected, but was" messages, you get "Expected invocation on the mock at least once, but was never performed". Not too tough to deal with once you get used to it, but maybe a little unclear if you're not used to seeing that type of message. – rsbarro Apr 18 '11 at 23:15
  • 1
    Yes, mocking framework messages can be a bit strange. You can add a fail message to the `Verify` method if that helps – aqwert Apr 18 '11 at 23:19
  • Yea, the fail message does help. I'm going to leave this question open for a bit to see if there are any other suggestions. Thanks again. – rsbarro Apr 18 '11 at 23:44
3

Just to elaborate on @rsbarro's comment - the Moq failure error message:

Expected invocation on the mock at least once, but was never performed

... is less than helpful for complex types, when determining exactly which condition actually failed, when hunting down a bug (whether in the code or unit test).

I often encounter this when using Moq Verify to verify a large number of conditions in the Verify, where the method must have been called with specific parameter values which are not primitives like int or string. (This is not typically a problem for primitive types, since Moq lists the actual "performed invocations" on the method as part of the exception).

As a result, in this instance, I would need to capture the parameters passed in (which to me seems to duplicate the work of Moq), or just move the Assertion inline with Setup / Callbacks.

e.g. the Verification:

widgetCreator.Verify(wc => wc.Create(
      It.Is<Widget>(w => w.DerivedName == "Derived.Name"
                    && w.SomeOtherCondition == true),
      It.Is<AnotherParam>(ap => ap.AnotherCondition == true),
  Times.Exactly(1));

Would be recoded as

widgetCreator.Setup(wc => wc.Create(It.IsAny<Widget>(),
                                    It.IsAny<AnotherParam>())
             .Callback<Widget, AnotherParam>(
              (w, ap) =>
                {
                  Assert.AreEqual("Derived.Name", w.DerivedName);
                  Assert.IsTrue(w.SomeOtherCondition);
                  Assert.IsTrue(ap.AnotherCondition, "Oops");
                });

// *** Act => invoking the method on the CUT goes here

// Assert + Verify - cater for rsbarro's concern that the Callback might not have happened at all
widgetCreator.Verify(wc => wc.Create(It.IsAny<Widget>(), It.Is<AnotherParam>()),
  Times.Exactly(1));

At first glance, this violates AAA, since we are putting the Assert inline with the Arrange (although the callback is only invoked during the Act), but at least we can get to the bottom of the issue.

Also see Hady's idea of moving the 'tracking' callback lambda into its own named function, or better still, in C#7, this can be moved to a Local Function at the bottom of the unit test method, so the AAA layout can be retained.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
3

Because of the way your code is structured, you're kind of forced to test two things in one unit test. You're testing that A) your presenter is calling the injected WidgetCreator's create method and B) that the correct name is set on the new Widget. If possible, it'd be better if you can somehow make these two things two separate tests, but in this case I don't really see a way to do that.

Given all that, I think the second approach is cleaner. It's more explicit as to what you're expecting, and if it fails, it'd make perfect sense why and where it's failing.

BFree
  • 102,548
  • 21
  • 159
  • 201
  • I agree that the second approach is preferable. I like @aqwert's suggestion of just using Verify, but the fail messages are too difficult to work with. I added some code to the original question that updates the second approach so I only have to declare the expression once. Think I'll stick with that approach. – rsbarro Apr 23 '11 at 04:36
1

Building on top of StuartLC's answer in this thread, you follow what he is suggesting without violating AAA by writing an "inline" function that is passed to the Verify method of a mock object.

So for example:

// Arrange
widgetCreator
  .Setup(wc => wc.Create(It.IsAny<Widget>(), It.IsAny<AnotherParam>());

// Act
// Invoke action under test here...

// Assert
Func<Widget, bool> AssertWidget = request =>
{
  Assert.AreEqual("Derived.Name", w.DerivedName);
  Assert.IsTrue(w.SomeOtherCondition);
  Assert.IsTrue(ap.AnotherCondition, "Oops");
  return true;
};

widgetCreator
  .Verify(wc => wc.Create(It.Is<Widget>(w => AssertWidget(w)), It.Is<AnotherParam>()), Times.Exactly(1));
Community
  • 1
  • 1
Hady
  • 2,597
  • 2
  • 29
  • 34