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 ss logic before I finally came up with this implemyou in your practice of TDD.
Previous Posts in this Series: Day 13 – More Stub Features
The last couple posts have seen us using basic Stubs to mock out the functionality a class that our code under test is dependent on. We focused on dealing with an external resource and being able to replicate enough of that resources functionality to satisfy our test. In this post I’ll introduce a new, simple test case. Over this and the next post you’ll see that not all “simple” test cases are so simple.
When a user places an order for an item and the quantity ordered is zero, then an InvalidOrderException should be thrown.On the surface, this seems like a very simple requirement; each order has a list of items, if any of the item in the list have a zero quantity then throw an exception. In most cases we would expect some sort of message to come back with the exception explaining what the issue is, but for now to keep the example simple, we’ll ignore that best practice (it will be addressed in a future post). Seems pretty simple, right? Well then let’s write our test. We can start with the basics:
[Test]
[ExpectedException(
typeof
(InvalidOrderException))]
public
void
WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
//Arrange
//Act
//Assert
}
(get code sample) [Test]
[ExpectedException(
typeof
(InvalidOrderException))]
public
void
WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
//Arrange
//Act
//Assert
Mock.Assert(orderDataService);
}
(get sample code) [Test]
[ExpectedException(
typeof
(InvalidOrderException))]
public
void
WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
//Arrange
//Act
orderService.PlaceOrder(customerId, shoppingCart);
//Assert
Mock.Assert(orderDataService);
}
(get sample code) [TestFixture]
class
OrderServiceTests
{
private
OrderService _orderService;
[TestFixtureSetUp]
public
void
SetupTestFixture()
{
_orderService =
new
OrderService();
}
}
(get sample code) [TestFixture]
class
OrderServiceTests
{
private
OrderService _orderService;
private
IOrderDataService _orderDataService;
[TestFixtureSetUp]
public
void
SetupTestFixture()
{
_orderDataService = Mock.Create<IOrderDataService>();
_orderService =
new
OrderService(_orderDataService);
}
}
(get sample code)
Once that’s done I refactor my existing test to use these instance variable instead of the local instances:
[Test]
public
void
WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned()
{
//Arrange
var shoppingCart =
new
ShoppingCart();
shoppingCart.Items.Add(
new
ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
var customerId = Guid.NewGuid();
var expectedOrderId = Guid.NewGuid();
Mock.Arrange(() => _orderDataService.Save(Arg.IsAny<Order>()))
.Returns(expectedOrderId)
.OccursOnce();
//Act
var result = _orderService.PlaceOrder(customerId, shoppingCart);
//Assert
Assert.AreEqual(expectedOrderId, result);
Mock.Assert(_orderDataService);
}
As you can see I’ve removed the lines of code that created the OrderDataService and the OrderService mocks. I’ve also updated the variable names on lines 10, 15 and 19 to reflect the new instance variable names. But after refactoring to use the instance variable my existing test does not passes. Instead I get a compilation error (Figure 1):
Figure 1 – Our solution no longer compiles
We haven’t needed a type of InvalidOrderException until now, but now that we have a test that specifically calls for it, we need to create one.
I like to keep my projects highly organized. As part of that I like to keep my custom exceptions in their own folder within the project (which also puts them in their own namespace). I create a folder in the TddStore.Core project called Exceptions. I add a new C# class to that folder (if you are using JustCode, you have select the folder and use the Ctrl + Alt + Ins keyboard shortcut to bring up the JustCode file template list, then select “C# Class”). I will call my new class InvalidOrderException, and (for now) the implementation is about as easy as you can possibly get:
using
System;
namespace
TddStore.Core.Exceptions
{
public
class
InvalidOrderException : Exception
{
}
}
(get sample code)
I simply need to add a using statement for the new TddStore.Core.Exceptions namespace. If you are using JustCode, simply put the cursor over the InvalidOrderException type in the argument of the ExpectedException attribute (it should be the thing with the red squiggly line under it) and hit Ctrl+` and select “Add using for TddStore.Core.Exceptions.
Once that’s done my original test passes, so I can move on back to my new test. First I need to refactor what I have already written to use the instance variables I just declared (shown below). Then I need to start working on my Arrange block. First I need to setup the variables for my call to PlaceOrder, so I’ll declare customerId and shoppingCart. While I’m at it, I’ll add an item to shoppingCart with a quantity of zero:
[Test]
[ExpectedException(
typeof
(InvalidOrderException))]
public
void
WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
//Arrange
var shoppingCart =
new
ShoppingCart();
shoppingCart.Items.Add(
new
ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
var customerId = Guid.NewGuid();
var expectedOrderId = Guid.NewGuid();
Mock.Arrange(() => _orderDataService.Save(Arg.IsAny<Order>()))
.Returns(expectedOrderId)
.OccursOnce();
//Act
_orderService.PlaceOrder(customerId, shoppingCart);
//Assert
Mock.Assert(_orderDataService);
}
(get sample code)
Next I need to setup my mock:
[Test]
[ExpectedException(
typeof
(InvalidOrderException))]
public
void
WhenAUserAttemptsToOrderAnItemWithAQuantityOfZeroThrowInvalidOrderException()
{
//Arrange
var shoppingCart =
new
ShoppingCart();
shoppingCart.Items.Add(
new
ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 0 });
var customerId = Guid.NewGuid();
var expectedOrderId = Guid.NewGuid();
Mock.Arrange(() => _orderDataService.Save(Arg.IsAny<Order>()))
.Returns(expectedOrderId)
.OccursNever();
//Act
_orderService.PlaceOrder(customerId, shoppingCart);
//Assert
Mock.Assert(_orderDataService);
}
Unlike the previous test I want to make sure that the Save method on the OrderDataService is not called at all. As you saw in the previous post, JustMock provides a fluent syntax that let me define a stub with an expectation of being called once. Similarly, I can use the OccursNever method (line 13) to state that this method should not be called in the scope of this test.
And there we have it; a pretty easy run-of-the mill test.
Running the test shows that if fails as I have not implemented the business logic yet. Following the idea of the simplest thing that might work, I went through a few iterations of business logic before I finally came up with this implementation:
public
Guid PlaceOrder(Guid customerId, ShoppingCart shoppingCart)
{
foreach
(var item
in
shoppingCart.Items)
{
if
(item.Quantity == 0)
{
throw
new
InvalidOrderException();
}
}
var order =
new
Order();
return
_orderDataService.Save(order);
}
Running my tests, I can see that they both pass (Figure 2):
Figure 2 – Passing tests
My work is done!
In the next post we’ll discover an issue with our test that may be giving us a false sense of security (If you know what it is, don’t spoil it!)
Continue the TDD journey:
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.