If you’ve never done Test Driven Development or aren’t even sure what this "crazy TDD stuff” is all about than this is the series for you. Over the next 30 days this series of posts take you from “I can spell TDD” to being able to consider yourself a “functional” TDD developer. Of course TDD is a very deep topic and truly mastering it will take quite a bit of time, but the rewards are well worth it. Along the way I’ll be showing you how tools like JustCode and JustMock can help you in your practice of TDD.

Previous Posts in this Series: Day 14 - “Simple” Does Not Always Mean “Obvious”" Pt. 1

In the last post we wrote what should have been a simple test. But in this case we’ll see how sometimes simple tests can be a little more complicated that we anticipate. 


So, What’s the Problem?

In the last post, we received this test case:

When a user places an order for an item and the quantity ordered is zero, then an InvalidOrderException should be thrown.

From this we ended up creating this test:

 1:         [Test]
 2:         [ExpectedException(typeof(InvalidOrderException))]
 3:  public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
 4:         {
 5:  //Arrange
 6:             var shoppingCart = new ShoppingCart();
 7:             shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
 8:             var customerId = Guid.NewGuid();
 9:             var expectedOrderId = Guid.NewGuid();
 10:  
 11:             Mock.Arrange(() => _orderDataService.Save(Arg.IsAny<Order>()))
 12:                 .Returns(expectedOrderId)
 13:                 .OccursNever();
 14:  
 15:  //Act
 16:             _orderService.PlaceOrder(customerId, shoppingCart);
 17:  
 18:  //Assert
 19:             Mock.Assert(_orderDataService);
 20:         }

(get sample code)

The test passed, but there is still a problem. We want to make sure that the PlaceOrder method on the OrderService class (our code under test) throws an InvalidOrderException and does not call the Save method on the OrderDataService class. The test seems to validate that; we have told the test to expect the InvalidOrderException to be thrown and have setup our stub to expect the Save method to never be called. The test passes, so what’s the problem?

The problem is that the test is marked as having passed, but the Mock.Assert call on line 19 is never called. Don’t believe me? Let’s try an experiment. Let’s change the OccursNever call on line 13 to be OccursOnce:

 1:         [Test]
 2:         [ExpectedException(typeof(InvalidOrderException))]
 3:  public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
 4:         {
 5:  //Arrange
 6:             var shoppingCart = new ShoppingCart();
 7:             shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
 8:             var customerId = Guid.NewGuid();
 9:             var expectedOrderId = Guid.NewGuid();
 10:  
 11:             Mock.Arrange(() => _orderDataService.Save(Arg.IsAny<Order>()))
 12:                 .Returns(expectedOrderId)
 13:                 .OccursOnce();
 14:  
 15:  //Act
 16:             _orderService.PlaceOrder(customerId, shoppingCart);
 17:  
 18:  //Assert
 19:             Mock.Assert(_orderDataService);
 20:         }

If our test is executing as we expect then the test should fail.

image

Figure 1 – Uh oh.

Since the test didn’t fail when we changed one our exceptions to the opposite of what it should be we have a problem. Clearly our Mock.Assert is not being called.

Turns out the problem is NUnit. Or, more specifically, the ExceptedException attribute of NUnit. What this attribute essentially does is tells the test runner to wrap your whole test method in a try/catch block and catch a specific exception type. The call to PlaceOrder on line 16 results in an exception being thrown. Since we are not catching that exception in our test it bubbles up to the test runner. Once the test runner catches the exception that it’s looking for it passes the test and does not execute any of the code following the line in the test the exception what thrown from.

In many cases using the ExpectedException attribute is fine. We have now encountered a situation where it is not, so we need to find another, more manual way. The first thing I need to do is remove the ExpectedException attribute from my test (line 2 in previous listing). I then need to wrap my call to PlaceOrder on line 17 in a try/catch block:

 1:         [Test]
 2:  public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
 3:         {
 4:  //Arrange
 5:             var shoppingCart = new ShoppingCart();
 6:             shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
 7:             var customerId = Guid.NewGuid();
 8:             var expectedOrderId = Guid.NewGuid();
 9:  
 10:             Mock.Arrange(() => _orderDataService.Save(Arg.IsAny<Order>()))
 11:                 .Returns(expectedOrderId)
 12:                 .OccursOnce();
 13:  
 14:  //Act
 15:  try
 16:  {
 17:  _orderService.PlaceOrder(customerId, shoppingCart);
 18:  }
 19:  catch(InvalidOrderException ex)
 20:  {
 21:  //Assert
 22:  Mock.Assert(_orderDataService);
 23:  Assert.Pass();
 24:  }
 25:  
 26:  //Assert
 27:  Assert.Fail();
 28:         }

(get sample code)

To verify my code I want to specifically catch an InvalidOrderExpection exception being thrown from the PlaceOrder method. In this test I setup my catch to catch that specific type of exception (all other exceptions get bubbled up to the test runner and fail the test). After catching this exception I want to assert that my stub was properly used, so I call Mock.Assert. If that works I need to tell my test runner that the test has passed, so I call Assert.Pass which tells the test runner the method is over and the test passed. If another type of exception is thrown or the method doesn’t throw an error the code falls through to the Assert.Fail call on line 27 and the test is failed.

We’ll go ahead and run this test, and see that if fails (Figure 2):

image

Figure 2 – Our test still fails

This fails because the setup for our stub still expects Save to be called exactly once. I left this here so we could see that with the newly refactored test we are in fact asserting that our stubs are being used properly. Changing the setup for the mock back to expect the Save method to never be called puts our test back the way we actually want it (line 12):

 1:         [Test]
 2:  public void WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
 3:         {
 4:  //Arrange
 5:             var shoppingCart = new ShoppingCart();
 6:             shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
 7:             var customerId = Guid.NewGuid();
 8:             var expectedOrderId = Guid.NewGuid();
 9:  
 10:             Mock.Arrange(() => _orderDataService.Save(Arg.IsAny<Order>()))
 11:                 .Returns(expectedOrderId)
 12:                 .OccursNever();
 13:  
 14:  //Act
 15:  try
 16:             {
 17:                 _orderService.PlaceOrder(customerId, shoppingCart);
 18:             }
 19:  catch(InvalidOrderException ex)
 20:             {
 21:  //Assert
 22:                 Mock.Assert(_orderDataService);
 23:                 Assert.Pass();
 24:             }
 25:  
 26:  //Assert
 27:             Assert.Fail();
 28:         }

(get sample code)

We run the test and we can see that it is now passing (Figure 3):

image

Figure 3 – Passing test

Summary

NUnit provides a lot of things to help us write better, easier to understand tests. But it’s important to understand how these things work and when they are not appropriate to use. As this series progresses I’ll point out more idiosyncrasies with the NUnit framework and how to negotiate them.

 

Continue the TDD journey:

JustCode download banner image

JustMock banner


About the Author

James Bender

is a Developer and has been involved in software development and architecture for almost 20 years. He has built everything from small, single-user applications to Enterprise-scale, multi-user systems. His specialties are .NET development and architecture, TDD, Web Development, cloud computing, and agile development methodologies. James is a Microsoft MVP and the author of two books; "Professional Test Driven Development with C#" which was released in May of 2011 and "Windows 8 Apps with HTML5 and JavaScript" which will be available soon. James has a blog at JamesCBender.com and his Twitter ID is @JamesBender. Google Profile

Comments

Comments are disabled in preview mode.