Telerik blogs

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 15: “Simple” Does Not Always Mean “Obvious” Pt. 2

In a previous post we created a relatively simple stub to stand in for our OrderDataService. Our current stub is setup to return a canned result without worrying too much about what the value of our input parameter is. There are cases where a mock like this acceptable, but in most cases you’ll find that you want to have a specific response for a specific parameter. Luckily that’s not very difficult and today we will create a stub with that exact ability.


Moving Right Along…

We’ll be continuing with the example from our previous few posts. For review, our business requirement can be found in this post. As part of this feature we need to integrate with an external order fulfillment system. The next test cases I’ve decided to tackle are around interacting with the order fulfillment system. Instead of building our own order fulfillment infrastructure, management has decided to outsource this. This means that the order fulfillment system we will need to deal with is an external web service. It also means that we will need to provide with some customer information, notably name and address.

This means that we now need to interact with another external resource, the CustomerService to retrieve information about our customer. To help use with this we’ve been supplied with an Address and a Customer class:

 1: using System;
 2:  
 3: namespace TddStore.Core
 4: {
 5:  public class Customer
 6:     {
 7:  public Guid Id { get; set; }
 8:  public string FirstName { get; set; }
 9:  public string LastName { get; set; }
 10:  public Address ShippingAddress { get; private set; }
 11:  
 12:  public Customer()
 13:         {
 14:             ShippingAddress = new Address();
 15:         }
 16:     }
 17:  
 18:  public class Address
 19:     {
 20:  public string StreetAddressOne { get; set; }
 21:  public string StreetAddressTwo { get; set; }
 22:  public string City { get; set; }
 23:  public string State { get; set; }
 24:  public string Zip { get; set; }
 25:     }
 26: }

(get sample code)

Next we need an interface to the Customer Service:

 1: using System;
 2:  
 3: namespace TddStore.Core
 4: {
 5:  public interface ICustomerService
 6:     {
 7:         Customer GetCustomer(Guid customerId);
 8:     }
 9: }

(get sample code)

Both entities and the interface for the Customer Service were added to the TddStore.Core project.

At this point I should start thinking about the first test case for interacting with my OrderFulfillment Service, which oddly enough has nothing to do with the OrderFulfillment Service. In this case I want to start by making sure that my OrderService is properly using my CustomerService. I’ll tackle the “happy path” first:

When a valid customer places an order (that passes validation) then order should be placed and an order number should be returned.

I need a new test:

 1:         [Test]
 2:  public void WhenAValidCustomerPlacesAValidOrderAnTheOrderServiceSholdBeAbleToGetACustomerFromTheCustomerService()
 3:         {
 4:  //Arrange
 5:             var shoppingCart = new ShoppingCart();
 6:             shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
 7:             var customerId = Guid.NewGuid();
 8:  
 9:  //Act
 10:             _orderService.PlaceOrder(customerId, shoppingCart);
 11:         }

(get sample code)

Yes, this looks similar to the beginnings of the WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned test. No, I shouldn’t just change the WhenUserPlacesACorrectOrderThenAnOrderNumberShouldBeReturned test to also test the CustomService interaction. The purpose of the original test is to insure that the basic interaction with the OrderDataSevice works. The details of those interactions may change independently of how the method interacts with the CustomerService. As these tests evolve the need to create more involved mocks for the OrderDataService will be confined to tests that verify the interaction with the OrderDataService and vice versa. This enables both interactions (from OrderService to OrderDataService and from OrderService to CustomerService) to change (mostly) independently of each other, which leads to less brittle tests. It also helps me when a test fails as I’ll know more precisely if it was the interaction with the OrderDataService or the CustomerService that broke it, helping me know where to look first.

Before I go any further, I need to declare an instance variable for my CustomerService mock in the OrderServiceTests class. I also need to instantiate it in the SetupTestFixture method:

 1:     [TestFixture]
 2:  class OrderServiceTests
 3:     {
 4:  private OrderService _orderService;
 5:  private IOrderDataService _orderDataService;
 6:  private ICustomerService _customerService;
 7:  
 8:         [TestFixtureSetUp]
 9:  public void SetupTestFixture()
 10:         {
 11:             _orderDataService = Mock.Create<IOrderDataService>();
 12:             _customerService = Mock.Create<ICustomerService>();
 13:             _orderService = new OrderService(_orderDataService);
 14:         }

(get sample code)

I know that for my new requirements based around my OrderFultillment service I will need to retrieve a customer from the CustomerService. I’ve already declared a mock for the CustomerService so right now I just need to arrange it. In this case I want my stub to return a specific instance of Customer when the GetCustomer method is called with a specific parameter. In the code I took from the other test I’ve already declared a customer id. The next step is to create an instance of Customer for my stub to return:

 1:         [Test]
 2:  public void WhenAValidCustomerPlacesAValidOrderAnTheOrderServiceSholdBeAbleToGetACustomerFromTheCustomerService()
 3:         {
 4:  //Arrange
 5:             var shoppingCart = new ShoppingCart();
 6:             shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
 7:             var customerId = Guid.NewGuid();
 8:  var customerToReturn = new Customer { Id = customerId, FirstName = "Fred", LastName = "Flinstone" };
 9:  
 10:  //Act
 11:             _orderService.PlaceOrder(customerId, shoppingCart);
 12:         }

(get sample code)

In my creation of the customerToReturn object I populated the Customer object with the Id (in this case the same customer Id I declared on line 7) and a first and last name. I don’t technically need first or last name right now, but I like to populate as many fields on an object as I can in testing. The reason is that while there’s nothing in my code that uses or even cares about those fields right now that could change. The logic this test is designed to check could be changed down the road by another test that makes the values in these fields meaningful. If I didn’t populate those values here there’s a change this test could fail. Or potentially, not fail when it should!

Now that I have my input and output values defined I can arrange my stub. In this case I only want my CustomerService stub to return the cutsomterToReturn when the GetCustomer method is called with the customerId value. I also expect that this method will be called exactly once:

 1:         [Test]
 2:  public void WhenAValidCustomerPlacesAValidOrderAnTheOrderServiceSholdBeAbleToGetACustomerFromTheCustomerService()
 3:         {
 4:  //Arrange
 5:             var shoppingCart = new ShoppingCart();
 6:             shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
 7:             var customerId = Guid.NewGuid();
 8:             var customerToReturn = new Customer { Id = customerId, FirstName = "Fred", LastName = "Flinstone" };
 9:  
 10:  Mock.Arrange(() => _customerService.GetCustomer(customerId))
 11:                 .Returns(customerToReturn)
 12:                 .OccursOnce();
 13:  
 14:  //Act
 15:             _orderService.PlaceOrder(customerId, shoppingCart);
 16:         }

(get sample code)

Lastly I need to assert that my new mock was called properly. I can do that by calling Mock.Assert and passing in the CustomerService mock:

 1:         [Test]
 2:  public void WhenAValidCustomerPlacesAValidOrderAnTheOrderServiceSholdBeAbleToGetACustomerFromTheCustomerService()
 3:         {
 4:  //Arrange
 5:             var shoppingCart = new ShoppingCart();
 6:             shoppingCart.Items.Add(new ShoppingCartItem { ItemId = Guid.NewGuid(), Quantity = 1 });
 7:             var customerId = Guid.NewGuid();
 8:             var customerToReturn = new Customer { Id = customerId, FirstName = "Fred", LastName = "Flinstone" };
 9:  
 10:             Mock.Arrange(() => _customerService.GetCustomer(customerId))
 11:                 .Returns(customerToReturn)
 12:                 .OccursOnce();
 13:  
 14:  //Act
 15:             _orderService.PlaceOrder(customerId, shoppingCart);
 16:  
 17:  //Assert
 18:  Mock.Assert(_customerService);
 19:         }

(get sample code)

Unsurprisingly, the test fails when I run it (Figure 1):

image

Figure 1 – The test fails

My CustomerService stub was not called. I need to go to the PlaceOrder method of OrderService class and implement the logic to call the CustomerService:

 1:  public Guid PlaceOrder(Guid customerId, ShoppingCart shoppingCart)
 2:         {
 3:  foreach (var item in shoppingCart.Items)
 4:             {
 5:  if (item.Quantity == 0)
 6:                 {
 7:  throw new InvalidOrderException();
 8:                 }
 9:             }
 10:  
 11:  var customer = _customerService.GetCustomer(customerId);
 12:  
 13:             var order = new Order();
 14:  return _orderDataService.Save(order);
 15:         }

(get sample code)

At this point I can’t run my tests because my code will not compile. I don’t have a reference to the CustomerService in my OrderService. So, the next step is to change the OrderService class to accept the ICustomerService interface as a dependency which I’ll inject via the constructor (I’ve removed the PlaceOrder method from this listing to make this listing a little clearer):

 1: using System;
 2: using TddStore.Core.Exceptions;
 3:  
 4: namespace TddStore.Core
 5: {
 6:  public class OrderService
 7:     {
 8:  private IOrderDataService _orderDataService;
 9:  private ICustomerService _customerService;
 10:  
 11:  public OrderService(IOrderDataService orderDataService, ICustomerService customerService)
 12:         {
 13:             _orderDataService = orderDataService;
 14:  _customerService = customerService;
 15:         }
 16:     }
 17: }

(get sample code)

Running my tests again shows me that my code still doesn’t compile. The reason is that in SetupTestFixture method of our OrderServiceTests we are trying to create an instance of OrderService without providing an instance ICustomerService to the constructor. I need change this method to provide a mock to the OrderService constructor (I’ve removed the using statements and test methods from this listing in the interest of clarity):

 1: namespace TddStore.UnitTests
 2: {
 3:     [TestFixture]
 4:  class OrderServiceTests
 5:     {
 6:  private OrderService _orderService;
 7:  private IOrderDataService _orderDataService;
 8:  private ICustomerService _customerService;
 9:  
 10:         [TestFixtureSetUp]
 11:  public void SetupTestFixture()
 12:         {
 13:             _orderDataService = Mock.Create<IOrderDataService>();
 14:  _customerService = Mock.Create<ICustomerService>();
 15:             _orderService = new OrderService(_orderDataService, _customerService);
 16:         }
 17:     }
 18: }

(get sample code)

Now my test is ready to run! And as you can see in Figure 2, it passes:

image

Figure 2 – The test passes.

One thing you might be asking is why I didn’t capture the result of the PlaceOrder method like I did in the original test. The reason is that for this test I didn’t care what the order number was; I was only testing the interaction with the CustomerService. In fact, but not needing an order number I also didn’t need to arrange a mock for the OrderDataService, test the result value against an expected result or assert the the mock for the OrderDataService was invoked properly. I was able to write a test that was focused on one thing; the interaction with the CustomerService and was able to insulate myself from potential changes in the OrderDataService. Will there come a day when the requirements dictate that this test arrange a mock of the OrderDataService? Perhaps, but until that day comes we aren’t going to worry about it.

Summary

We accomplished a lot in this post; we started tackling one of the more complicated test cases for this feature, integrated with an external resource and wrote a slightly more intelligent stub capable of responding to specific parameters. As mentioned, finishing this test case will take a few more steps, but we are well on our way to being able to complete one more step in developing this application.

 

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.