The reason it fails when you ask for actual.Orders.Count()
is because actual
returns this object which you created earlier:
new Customer() { Id = 1, Name = "Jeff" }
And that concrete Customer
object has null
for its Orders
property, because you never set it up.
Look at it like this. As far as your code is concerned, the repository is just a storage mechanism for objects. It doesn't care whether the storage is backed by tables in a database with relationships between those tables, or if it's just put in a list somewhere. What's important is that when you call repo.GetCustomer(1)
, it returns to you a Customer
object with an ID of 1 and all the other details filled out as they should be. In this case, you need the related orders to actually be in the Customer
object!
So you could do something like:
private void FillData()
{
var customer1 = new Customer() { Id = 1, Name = "Jeff" };
var customer2 = new Customer() { Id = 2, Name = "Brian" };
var order1 = new Order() { Id = 1, Customer = 1, Value = 100 };
var order2 = new Order() { Id = 2, Customer = 2, Value = 200 };
var order3 = new Order() { Id = 3, Customer = 1, Value = 300 };
customer1.Orders = new List<Order> {order1, order3};
customer2.Orders = new List<Order> {order2};
this.Customers = new FakeObjectSet<Customer>(new[] {customer1, customer2});
this.Orders = new FakeObjectSet<Order>(new[] {order1, order2, order3});
}
But with your full set of orders.
ALL THAT being said, I strongly suggest against using hand-rolled concrete mocks like this. Look into using the Moq framework: http://code.google.com/p/moq/ It'll make your life a lot easier.
Edit: Here's something you might find useful in building contexts for your unit tests. First off, if you're not aware of what an extension method is, this is just an example:
namespace Foo
{
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string input)
{
return string.IsNullOrEmpty(input);
}
}
}
A consumer then could do...
using Foo;
string a = null, b = "hello";
a.IsNullOrEmpty(); // returns true
b.IsNullOrEmpty(); // returns false
The syntax allows you to create methods that can be called as if they were instance methods on an object, but in fact are static methods that are defined elsewhere.
NOW, that being said. You could potentially make some extension methods and helper classes to build contexts for your unit tests. As an example.
public static class UnitTestHelper
{
private static int _nextCustomerId = 0;
private static int _nextOrderId = 0;
public static Customer MockCustomer(string name)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentException("name");
var id = _nextCustomerId;
_nextCustomerId += 1;
return new Customer
{
Id = id,
Name = name,
Orders = new List<Order>()
};
}
public static Customer WithOrder(this Customer customer, int value)
{
if (customer == null) throw new ArgumentNullException("customer");
var order = new Order
{
Id = _nextOrderId,
Customer = customer.Id,
Value = value
};
customer.Orders.Add(order);
_nextOrderId += 1;
return customer;
}
public static Mock<Repository> HavingCustomers(this Mock<Repository> repository,
params Customer[] customers)
{
if (repository == null) throw new ArgumentNullException("repository");
var allOrders = customers.SelectMany(c => c.Orders);
repository.Setup(r => r.Customers)
.Returns(new FakeObjectSet<Customer>(customers));
repository.Setup(r => r.Orders)
.Returns(new FakeObjectSet<Order>(allOrders));
return repository;
}
}
Once you've got that, instead of having to do a lot of painstaking manual creation of stuff, you can do something like...
[Test]
public void ShouldReturnAllCustomersWithoutOrders()
{
var john = UnitTestHelper.MockCustomer("John").WithOrder(100).WithOrder(200);
var paul = UnitTestHelper.MockCustomer("Paul");
var george = UnitTestHelper.MockCustomer("George").WithOrder(15);
var ringo = UnitTestHelper.MockCustomer("Ringo");
var mockRepository = new Mock<Repository()
.HavingCustomers(john, paul, george, ringo);
var custServ = new CustomerService(mockRepository.Object);
var customersWithoutOrders = custServ.GetCustomersWithoutOrders();
Assert.That(customersWithoutOrders.Count(), Is.EqualTo(2));
Assert.That(customersWithoutOrders, Has.Member(paul));
Assert.That(customersWithoutOrders, Has.Member(ringo));
}
And that setup could be extracted out into a method with the SetUpAttribute
attached if it's going to be used in multiple tests.
You're going to want as much flexibility as possible when you're defining the context for your unit tests, you don't want to assume that for every unit test you write you always want the same two customers with the same eight orders. But that doesn't mean you can't write some quick helper methods or classes to make the setup easier and much less verbose.
Hope that helps!