Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TDD: Stub, Mock, or None of the Above

Tags:

c#

tdd

mocking

stub

I'm trying to learn TDD by applying it to a simple project of mine. Some details (and an earlier question) are here:

TDD: Help with writing Testable Class

The specifics are I have a PurchaseOrderCollection class that has a private List of PurchaseOrders (passed in at constructor), and the PurchaseOrders have a boolean property IsValid. The PurchaseOrderCollection has a property HasErrors that returns a true if any PurchaseOrders in the list have IsValid as false. This is the logic I want to test.

[TestMethod]
public void Purchase_Order_Collection_Has_Errors_Is_True_If_Any_Purchase_Order_Has_Is_Valid_False()
{
    List<PurchaseOrder> orders = new List<PurchaseOrder>();

    orders.Add(new PurchaseOrder(--some values to generate IsValid false--));
    orders.Add(new PurchaseOrder(--some values to generate IsValid true--));

    PurchaseOrderCollection collection = new PurchaseOrderCollection(orders);

    Assert.IsTrue(collection.HasErrors);
}

This is similar to my previous question in that this test is too coupled in that I have to know the logic of what makes a PurchaseOrder IsValid false or true to pass the test, when really this test shouldn't care. The question is different (imo) in that the classes themselves aren't the problem.

Essentially I want to be able to declare a PurchaseOrder that has IsValid false or true without knowing anything more about what a PurchaseOrder is.

From my limited TDD knowledge, this is something you use Stubs or Mocks for. My main question, is this correct? Or should I be using a different method for this? Or am I completely flawed and am just writing this test and thinking about it wrong?

My initial thought was to just use some kind of mock framework and create a PurchaseOrder that always returns true or false. From what I've read though, I'd need to declare IsValid virtual. So my second thought was to change it to add IPurchaseOrder as an interface for PurchaseOrder and just create a fake PurchaseOrder that always returns false or true. Are both of these valid ideas?

Thanks!

like image 216
anonymous Avatar asked Dec 23 '22 13:12

anonymous


2 Answers

You are on the right track with either creating a stub or a mock. I prefer using a Mocking framework.

How it would work using a mocking framework is that you would want to mock your PurchaseOrder class therefore abstracting away it's implementation. Then setup expectations that IsValid is called and when it is called return this value.

Example using Moq, if you're using C# 3.0 and .NET Framework 3.5:

[TestMethod]
public void Purchase_Order_Collection_Has_Errors_Is_True_If_Any_Purchase_Order_Has_Is_Valid_False()
{    
    var mockFirstPurchaseOrder = new Mock<IPurchaseOrder>();
    var mockSecondPurchaseOrder = new Mock<IPurchaseOrder>();

    mockFirstPurchaseOrder.Expect(p => p.IsValid).Returns(false).AtMostOnce();
    mockSecondPurchaseOrder.Expect(p => p.IsValid).Returns(true).AtMostOnce();

    List<IPurchaseOrder> purchaseOrders = new List<IPurchaseOrder>();
    purchaseOrders.Add(mockFirstPurchaseOrder.Object);
    purchaseOrders.Add(mockSecondPurchaseOrder.Object);

    PurchaseOrderCollection collection = new PurchaseOrderCollection(orders);

    Assert.IsTrue(collection.HasErrors);
}

Edit:
Here I used an interface to create the mock of PurchaseOrder, but you don't have too. You could mark IsValid as virtual and mock the PurchaseOrder class. My rule of thumb when which way to go is to use virtual first. Just to create an interface, so I can mock an object without any architectual reason is a code smell to me.

like image 81
Dale Ragan Avatar answered Dec 31 '22 15:12

Dale Ragan


...this test is too coupled in that I have to know the logic of what makes a PurchaseOrder IsValid false or true to pass the test, when really this test shouldn't care...

I'd actually argue the reverse -- that for your test to know that validity is modeled as a boolean within the purchase order means that your test knows too much about the implementation of PurchaseOrder (given that it's actually a test of PurchaseOrderCollection). I don't have a problem with using real-world knowledge (i.e. actual values that would be valid or invalid) to create appropriate test objects. Ultimately, that's really what you're testing (if I give my collection a purchase order with ridiculous values, will it correctly tell me there are errors).

In general I try to avoid writing an interface for an "entity" object such as a PurchaseOrder unless there's some reason to do so other than for testing (e.g. there are multiple kinds of PurchaseOrders in production and an interface is the best way to model that).

It's great when testing reveals that your production code could be better designed. It's not so good, though, to change your production code just to make a test possible.

As if I haven't written enough, here's another suggestion -- and this is the way I would actually solve this in real life.

Create a PurchaseOrderValidityChecker that has an interface. Use that when setting the isValid boolean. Now create a test version of the validity checker that lets you specify which answer to give. (Note that this solution probably also requires a PurchaseOrderFactory or the equivalent for creating PurchaseOrders, so that each purchase order can be given a reference to the PurchaseOrderValidityChecker when it is created.)

like image 37
Jacob Mattison Avatar answered Dec 31 '22 14:12

Jacob Mattison