0

I have created a fake for my EF repository in a manner similar to that in Julie Lerman's blog post on EF4 mocks and unit tests.

My question is how can I get the fake repository to handle the relationships between the tables?

Say I have two tables with Customers and Orders. There is a 1 to many relationship between them so that a customer can have multiple orders.

My fake repository would be set up something like:

public class FakeMyRepository : IMyRepository
{
  public FakeMyRepository()
  {
    Committed = false;

    FillCustomers();
    FillOrders();
  }

  public bool Committed { get; set; }

  public System.Data.Objects.IObjectSet<Customer> Customers { get; set; }
  public System.Data.Objects.IObjectSet<Order> Orders { get; set; }

  public void Commit()
  {
    Committed = true;
  }

  private void FillCustomers()
  {
    var data = new List<Customer>()
    {
      new Customer() { Id = 1, Name = "Jeff" },
      new Customer() { Id = 2, Name = "Brian" }
    }
    this.Customers = new FakeObjectSet<Customer>(data);
  }

  private void FillOrders()
  {
    var data = new List<Order>()
    {
      new Order() { Id = 1, Customer = 1, Value = 100 }
      new Order() { Id = 2, Customer = 2, Value = 200 }
      new Order() { Id = 3, Customer = 1, Value = 300 }
      new Order() { Id = 4, Customer = 2, Value = 400 }
      new Order() { Id = 5, Customer = 1, Value = 500 }
    }
    this.Orders = new FakeObjectSet<Order>(data);
  }

}

If my test is like this it passes:

[TestMethod]
public void FindUserByIdTest()
{
  var repo = new FakeMyRepository();
  var target = new CustomerService(repo);

  var actual = target.GetCustomerById(1);

  Assert.IsNotNull(actual);
  Assert.AreEqual<string>("Jeff",actual.Name);
}

But if I want say the count of orders then it fails

[TestMethod]
public void FindUserByIdWithOrderCount()
{
  var repo = new FakeMyRepository();
  var target = new CustomerService(repo);

  var actual = target.GetCustomerById(1);

  Assert.IsNotNull(actual);
  Assert.AreEqual<int>(3,actual.Orders.Count());
}

Can anyone point me in the right direction for this?

Cheers.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
Nick
  • 4,115
  • 10
  • 45
  • 57

3 Answers3

1

Your fake repository must return customers with orders navigation property filled. Anyway this is typical scenario which doesn't make sense to unit test because either eager or lazy loading is leaky abstraction of your persistence layer. Eager loading (Include) works only with linq-to-entities and lazy loading happens completely outside of tested code.

Btw. something about unit testing and Entity framework.

Community
  • 1
  • 1
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • I'm not testing the repository, I'm testing the service method. In my example it's dumbed down to a GetCustomerById() function but it could be something much more involved. I'm just looking to see if the fake repo can be constructed that would negate me from having to write tons of mocking code for every unit test. – Nick Aug 24 '11 at 09:07
  • @Nick: Yes fake repo can be constructed that way but it will return relations **every time** so you will not test correct code. – Ladislav Mrnka Aug 24 '11 at 09:33
1

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!

BishopRook
  • 1,240
  • 6
  • 11
  • I use Moq in a bunch of places but I'm trying to avoid a load of setup code in every unit test that touches the repository in some way :\ – Nick Aug 24 '11 at 09:02
  • Unfortunately, that's the nature of unit tests. Every specific unit test is saying: "In this context, given these method calls, I should get this result." So you need to set up the context first. And *usually* it's a good idea to set up only the context you're expecting. I'm gonna add an edit that might help you out though. – BishopRook Aug 24 '11 at 13:34
0

Easy peasy. Just populate the orders when you initialize the customer

private void FillCustomers() 
  { 
    var data = new List<Customer>() 
    { 
      new Customer
      { 
        Id = 1, 
        Name = "Jeff",
        Orders=new List<Order>(new []
        {
           new Order() { Id = 1, Customer = 1, Value = 100 }  
           new Order() { Id = 3, Customer = 1, Value = 300 }  
           new Order() { Id = 5, Customer = 1, Value = 500 }            
        }
      }, 
      new Customer() { Id = 2, Name = "Brian" } 
    } 
    this.Customers = new FakeObjectSet<Customer>(data); 
  } 

And now your test should pass.

Michael Brown
  • 9,041
  • 1
  • 28
  • 37