Test Driven Development in .NET Part 6: verifying return values with mocks using the Moq framework
April 11, 2013 3 Comments
We will continue our discussion of mocking objects using the Moq framework. You can check out the absolute basics in the previous post.
In this post we’ll look at how we can test the return values of a dependency used within the system under test. We will extend our Product domain with a new property and we’ll let a dependency handle the construction of this new property. This property will be a unique ID. The expected behaviour is that if the ID is null then the Product creation logic should throw an exception. We’ll continue with the test-first approach.
Open the solution we started building in the previous post. Locate the When_creating_a_product.cs file. Add a new test method called An_exception_should_be_thrown_if_id_is_not_created():
[Test] public void An_exception_should_be_thrown_if_id_is_not_created() { }
We’ll start with the Arrange phase:
//Arrange ProductViewModel productViewModel = new ProductViewModel() { Description = "Nice product", Name = "ProductA" }; Mock<IProductIdBuilder> mockIdBuilder = new Mock<IProductIdBuilder>(); Mock<IProductRepository> mockProductRepository = new Mock<IProductRepository>();
The concrete class that builds the ID must implement the IProductIdBuilder interface. Go ahead and let Visual Studio create the interface using the built-in techniques. Make sure it is created in the Domain project.
There is a special method attribute in NUnit which declares that we’re expecting an exception to be thrown. If the exception is thrown then the test passes. So decorate the method as follows:
[Test] [ExpectedException(typeof(InvalidProductIdException))] public void An_exception_should_be_thrown_if_id_is_not_created()
Let VS create the InvalidProductIdException class for you. Add the following two lines to the test method body:
ProductService productService = new ProductService(mockProductRepository.Object); //Act productService.Create(productViewModel);
Run the tests now and you should see that the new test fails as it doesn’t throw any exception. Let’s carry on with the implementation: we now know that the ProductService will need a dependency of type IProductIdBuilder:
ProductService productService = new ProductService(mockProductRepository.Object , mockIdBuilder.Object);
Extend the IProductIdBuilder interface with the following method:
public interface IProductIdBuilder { ProductIdentifier BuildProductIdentifier(); }
Create the ProductIdentifier object with VS and add it to the property list of the Product object:
public class Product { public ProductIdentifier Identifier { get; set; } public string Name { get; set; } public string Description { get; set; } public Product(string name, string description) { Name = name; Description = description; } }
Re-run the tests just to see if anything has broken: we get the same failing test as before.
Open InvalidProductIdException.cs and have the class derive from Exception:
public class InvalidProductIdException : Exception { }
Finally go to ProductService where we need to look at a couple of things. Now we have two constructors. Rewrite the auto-created parameter names as follows:
private IProductRepository _productRepository; private IProductIdBuilder _productIdBuilder; public ProductService(IProductRepository productRepository) { // TODO: Complete member initialization _productRepository = productRepository; } public ProductService(IProductRepository productRepository, IProductIdBuilder productIdBuilder) { // TODO: Complete member initialization _productRepository = productRepository; _productIdBuilder = productIdBuilder; }
Finally we’ll need to call the _productIdBuilder.BuildProductIdentifier() method to build an ID and check if the ID is null:
public void Create(ProductViewModel productViewModel) { Product product = ConvertToDomain(productViewModel); product.Identifier = _productIdBuilder.BuildProductIdentifier(); if (product.Identifier == null) { throw new InvalidProductIdException(); } _productRepository.Save(product); }
Run the tests and the new unit test should fail as BuildProductIdentifier() returned null. However, now we have another failing test: Then_repository_save_should_be_called() fails because Create throws an exception. See how useful it is to have a unit test? Let’s try and accommodate the changes.
We’ll make one breaking change: we don’t need two constructors. Remove the single-parameter constructor from ProductService. This will break two tests: Then_product_repository_should_be_called_once_per_product() and Then_repository_save_should_be_called(). As we’re not testing the return value of the IProductIdBuilder interface in those we’ll need to pass in a valid implementation that returns a non-null object. Change the Arrange section of Then_repository_save_should_be_called() to the following:
//Arrange var mockProductRepository = new Mock<IProductRepository>(); mockProductRepository.Setup(p => p.Save(It.IsAny<Product>())); Mock<IProductIdBuilder> mockIdBuilder = new Mock<IProductIdBuilder>(); mockIdBuilder.Setup(x => x.BuildProductIdentifier()).Returns(new ProductIdentifier()); ProductService productService = new ProductService(mockProductRepository.Object, mockIdBuilder.Object);
The call to mockIdBuilder.Setup means that when BuildProductIdentifier() is called then please make sure that a new ProductIdentifier() object is returned.
To correct Then_product_repository_should_be_called_once_per_product() we simply need to pass in a non-null IProductIdBuilder to the ProductService to be able to compile:
var mockProductRepository = new Mock<IProductRepository>(); Mock<IProductIdBuilder> mockIdBuilder = new Mock<IProductIdBuilder>(); ProductService productService = new ProductService(mockProductRepository.Object, mockIdBuilder.Object);
This is because the CreateMany method of the ProductService doesn’t call the BuildProductIdentifier() method. We’ll revisit the CreateMany method later in the post.
Run the tests again and they should all pass.
Let’s take another look at An_exception_should_be_thrown_if_id_is_not_created(). It passes because Moq will return the default value of the ProductIdentifier when calling BuildProductIdentifier(). The default value of an object is of course null so the test passes implicitly. However, this may not be obvious to other developers so let’s set up this expection explicitly:
Mock<IProductIdBuilder> mockIdBuilder = new Mock<IProductIdBuilder>(); mockIdBuilder.Setup(i => i.BuildProductIdentifier()).Returns(() => null);
The Setup method will be familiar by now. The funny looking () => null is the lambda way of saying that the return value should be null. The test still passes but now we’re giving concise instructions about our expectations. You may be wondering where the ‘Assert’ section is. It is actually expressed by the [ExpectedException(typeof(InvalidProductIdException))] header above the method signature.
A second test worth writing is that the product should be saved if a valid non-null ID was created. Add another test method to When_creating_a_product.cs:
[Test] public void The_product_should_be_saved_if_id_was_created() { //Arrange ProductViewModel productViewModel = new ProductViewModel() { Description = "Nice product", Name = "ProductA" }; Mock<IProductRepository> mockProductRepository = new Mock<IProductRepository>(); Mock<IProductIdBuilder> mockIdBuilder = new Mock<IProductIdBuilder>(); mockIdBuilder.Setup(i => i.BuildProductIdentifier()).Returns(new ProductIdentifier()); ProductService productService = new ProductService(mockProductRepository.Object , mockIdBuilder.Object); //Act productService.Create(productViewModel); //Assert mockProductRepository.Verify(p => p.Save(It.IsAny<Product>())); }
The meaning of this test should be clear from the previous examples. We want to make sure that Save is called if ID is not null.
Out parameters
Out parameters are generally discouraged as they are a sign of a method trying to accomplish too much in its method body. However, the well-known .NET TryParse method also uses an out parameter so you may as well follow that convention and implement your own TryParse method that returns true if the value could be parsed. We’ll not create a specific method just for this purpose but you would set up the expectation on an out parameter as follows:
Mock<IManufacturerFactory> mockManufacturerFactory = new Mock<IManufacturerFactory>(); ProductManufacturer manufacturer = new ProductManufacturer() { Address = "Sweden" }; mockManufacturerFactory .Setup(m => m.TryParse(It.IsAny<string>(), out manufacturer))) .Returns(true);
I.e. we want to make sure that calling the TryParse method of the mock object implementing the IManufacturerFactory returns true for any string parameter.
Returning multiple values
In this section we’ll revisit the CreateMany method. After consulting the domain experts we’ve come to the conclusion that the Id of each product should be a unique integer, i.e. no two products should have the same Id. Open When_creating_multiple_products.cs and add another test method:
[Test] public void Then_each_product_should_receive_a_unique_id() { //Arrange List<ProductViewModel> productViewModels = new List<ProductViewModel>() { new ProductViewModel(){Name = "ProductA", Description="Great product"} , new ProductViewModel(){Name = "ProductB", Description="Bad product"} , new ProductViewModel(){Name = "ProductC", Description="Cheap product"} , new ProductViewModel(){Name = "ProductD", Description="Expensive product"} }; var mockProductRepository = new Mock<IProductRepository>(); Mock<IProductIdBuilder> mockIdBuilder = new Mock<IProductIdBuilder>(); ProductService productService = new ProductService(mockProductRepository.Object, mockIdBuilder.Object); }
The Arrange phase should be familiar but we’ll extend it here. We know that the ID will be of type integer so let’s define one:
Mock<IProductIdBuilder> mockIdBuilder = new Mock<IProductIdBuilder>(); int productId = 1;
Next we’ll set up our expectation that the ProductIdenfier object will hold unique ID values in each iteration. Add the following after the productId initialisation.
mockIdBuilder.Setup(i => i.BuildProductIdentifier()) .Returns(new ProductIdentifier() { RawValue = productId });
This will make sure that the ProductIdentifier will have a RawValue property of type int with a value of productId. Create the property using VS as usual. This call to Setup needs to be extended somehow as it assigns only the value of 1 nothing else whereas we need unique values in each iteration. We can achieve it as follows:
mockIdBuilder.Setup(i => i.BuildProductIdentifier()) .Returns(new ProductIdentifier() { RawValue = productId }) .Callback(() => new ProductIdentifier() { RawValue = productId++ })
The Callback method will make sure that productId will increase by one as we loop through the collection of Product objects. The full implementation of the test looks as follows:
[Test] public void Then_each_product_should_receive_a_unique_id() { //Arrange List<ProductViewModel> productViewModels = new List<ProductViewModel>() { new ProductViewModel(){Name = "ProductA", Description="Great product"} , new ProductViewModel(){Name = "ProductB", Description="Bad product"} , new ProductViewModel(){Name = "ProductC", Description="Cheap product"} , new ProductViewModel(){Name = "ProductD", Description="Expensive product"} }; var mockProductRepository = new Mock<IProductRepository>(); Mock<IProductIdBuilder> mockIdBuilder = new Mock<IProductIdBuilder>(); int productId = 1; mockIdBuilder.Setup(i => i.BuildProductIdentifier()) .Returns(new ProductIdentifier() { RawValue = productId }) .Callback(() => new ProductIdentifier() { RawValue = productId++ }); ProductService productService = new ProductService(mockProductRepository.Object, mockIdBuilder.Object); //Act productService.CreateMany(productViewModels); //Assert mockProductRepository.Verify(p => p.Save(It.IsAny<Product>()), Times.AtLeastOnce()); }
Run the tests and all 5 of them should pass. We don’t actually do anything with the Id in the CreateMany method so change the implementation to the following:
public void CreateMany(List<ProductViewModel> productViewModels) { foreach (ProductViewModel vm in productViewModels) { Product newProduct = new Product(vm.Name, vm.Description); newProduct.Identifier = _productIdBuilder.BuildProductIdentifier(); _productRepository.Save(newProduct); } }
This completes our discussion on mocking return values with Moq. In the next post we’ll look into how to verify arguments passed to the system under test and how to mock exceptions.
Hi, this test method Then_each_product_should_receive_a_unique_id dosen’t test that the RawValue of the identifier object must be unique.
I changed your method to:
public void CreateMany(List productViewModels)
{
var lastId = 0;
foreach (ProductViewModel vm in productViewModels)
{
Product newProduct = new Product(vm.Name, vm.Description);
newProduct.Identifier = _productIdBuilder.BuildProductIdentifier();
if (lastId != newProduct.Identifier.RawValue)
lastId = newProduct.Identifier.RawValue;
else
throw new Exception(“Duplicated ids”);
_productRepository.Save(newProduct);
}
}
The test method dosen’t should throw the exception, but it throws.
IDE: vs2010
Hello,
i might be a little bit late, but i still wanted to point out that
[ExpectedException(typeof(InvalidProductIdException))] might not be the best way to test for exceptions. It could be that several of your calls throw this exception, so you’re testing that this exception is thrown, but still don’t know which of those calls threw the exception. Rather test a call with AssertThrows to be sure the expected method throws the exception.
Hello,
Thanks for your remarks.
//Andras