开发者

How to use Moq to test a method with no return value?

开发者 https://www.devze.com 2023-04-02 14:34 出处:网络
This is my first question so please be kind! :) What I am trying to do is write some tests for a manager class that during construction adds many new instances of a single item class to a list.When t

This is my first question so please be kind! :)

What I am trying to do is write some tests for a manager class that during construction adds many new instances of a single item class to a list. When the UpdateAllItems is called in this manager class the intention is to iterate the list and call Increment on each single item.

The manager class is my code, but the single item class is not so I can't modify it.

I use NUnit for a testing framework and am starting to work with Moq. Because the manager class uses the single item class I would think I need to use a Moq so I am testing only the manager, not the single item.

How do I write tests for my UpdateAllItems method? (Technically I should be writing the tests first I know).

Here is a some sample code that gives a general idea of what I am working with...

public class SingleItem_CodeCantBeModified
{
    public int CurrentValue { get; private set; }

    public SingleItem_CodeCantBeModified(int startValue)
    {
        CurrentValue = startValue;
    }

    public void Increment()
    {
        CurrentValue++;
    }
}

public class SingleItemManager
{
    List<SingleItem_CodeCantBeModified> items = new List<SingleItem_CodeCantBeModified>();

    public SingleItemManager()
    {
        items.Add(new SingleItem_CodeCantBeModified(100));
        items.Add(new SingleItem_CodeCantBeModified(200));
    }

    public void UpdateAllItems()
    {
        items.ForEach(item => item.Increment());
    }
}开发者_如何学Python

Thanks in advance for all the help!


The simple answer is, you can't. The method that UpdateAllItems calls (Increment()) is non-virtual, so you won't be able to mock it.

Your options, as I see it, are:

  • Don't test UpdateAllItems at all. Its implementation is trivial, so this is an option to consider (though not ideal).
  • Create real SingleItem_CodeCantBeModified instances in your test. Purists would say that you no longer have a unit test at this point, but it could still be a useful test.
  • Add an ISingleItem interface, and an SingleItemAdapter : ISingleItem class that holds onto a reference to a SingleItem_CodeCantBeModified and forwards the calls. Then you can write SingleItemManager to operate on ISingleItems, and you'll be free to pass in mock ISingleItems in your tests. (Depending on how your system is set up, you might even be able to descend from SingleItem_CodeCantBeModified, implement the interface on your descendant, and use those objects instead of writing an adapter.)

That last option gives you the most options, but at the cost of some complexity. Choose the option that's best suited for what you're trying to accomplish.


Your Manager is too dependent on Item (in List<Item>). Can you extract list population into separate class to be able to mock it? e.g.:

public SingleItemManager()
{
    items.Add(ItemRepository.Get(100));
    items.Add(ItemRepository.Get(200));
}

Testing (some code omitted):

int i = 0;

var itemMock = new Mock<Item>();
itemMock.Setup(i => i.Increment()).Callback(() => i++);

var repositoryMock = new Moc<ItemRepository>();
repositoryMock.Setup(r => r.Get(It.IsAny<int>()).Returns(itemMock.Object);

var manager = new SingleItemManager();
manager.UpdateAllItems();

Assert.AreEqual(i, 1);


As usual, you can add another level of indirection.

  1. Create a wrapper class around SingleItem_CodeCantBeModified
  2. Make this wrapper inherit IItem interface
  3. Make SingleItemManager depend on IItem instead of SingleItem_CodeCantBeModified

OR

If Increment is a virtual method (I understand it isn't in your sample code, but just in case), use partial mocking.


Instead of hard-coding your additional concrete items, have the SingleItem_CodeCantBeModified implement an interface (or embed it in a wrapper which implements the interface) then pass in a (new) factory which will create these items.

In your test you will create a mock of the factory to pass in to your Manager class, then you can monitor what methods are called on that mocked object.

Although this would be more about testing the internals of the system, not the byproducts. What interface is the Manager implementing? If it's not proving itself externally, what results are you testing for?

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号