Proper way to verify parameters being passed to a Mock are set as expected

社会主义新天地 提交于 2019-12-05 06:32:39

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.

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);
}

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.

Hady

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));
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!