Testing LINQ to SQL with Mock Object – a complete solution

25 במרץ 2008

For the last few weeks I was looking for a complete solution to implement LINQ to SQL in our system. Before I started the searching process I have defined to myself some basic requirements from it:

· Rapid development in at least 80% of the cases.

· Easy to use, understandable API.

· Migration plain or good solution to work with our legacy code.

· Easy to maintenance.

· Testability framework!

One of the major disadvantages of the LINQ to SQL framework is the difficult to test it. If you’ll try to look over the web and to search of current solutions you probably find some good ones. The main idea in some of them is based on the Repository Pattern but these suggestions were lack. They suggest to wrap the Table class in the inherited data context class with IQueryable one. It helps to solve some of the problems with the fact that the Table class is a sealed class (as Ori Eini posted few months ago), but in this method you still have to work against active instance of SQL Server database (ever thought in some of the solution they used testing frameworks which based on rollback mechanism).

I decided to implement my own solution which includes two parts:

1. The ability to create database states for test the queries against it (without the need of active SQL Server in the air).

2. The ability to mock DML operations without actually execute them (and to create a database-less tests [= Tests that don’t have an active instance of SQL server to run]).

For the first part I used the Repository pattern (as the other guys suggested) and I referred the class which represent the table as an in-memory one. It’ll give us the ability to query it with LINQ to Object. To achieve the second part I mocked the SubmitChanges() method from the DataContect class.

Now I’m going to show you my implementation which gives you the ability to work with LINQ to SQL in testability environment.

1. I created an abstract class which represents one entity class in a single DataContext. This class implements the IDisposible interface which performs the dispose on the data context class.

   1:  public abstract class Repository<TEntity, TDataContext> : IDisposable
   2:      where TEntity : class
   3:      where TDataContext : DataContext, new()
   4:  {
   5:      
   6:  }

2. This class have two properties:


   1:  private IQueryable<TEntity> _Table;
   2:  public virtual IQueryable<TEntity> Table
   3:  {
   4:      get
   5:      {
   6:          return _Table;
   7:      }
   8:      set
   9:      {
  10:          _Table = value;
  11:      }
  12:  }
  13:   
  14:  private readonly DataContext _DataContext;
  15:  public DataContext GetDataContext
  16:  {
  17:      get { return _DataContext; }
  18:   
  19:  }
The Table property uses for Table wrapper class and the DataContext property will give us ability to use the data context for the entire class scope.

3. In order to initialize these two properties I created two constructors the first one is the default constructor which create a new data context object and initialize the Table property with the appropriate entity table (these two properties are defines as Generics types). The second constructor helps me to test my repository. It’ll get two parameters, the first one uses as mock data context and the second one as a table state (in-memory table like the database table state that we want to test).

   1:  public Repository()
   2:  {
   3:      _DataContext = new TDataContext();
   4:      _Table = _DataContext.GetTable<TEntity>();
   5:  }
   6:   
   7:  public Repository(TDataContext targetAdDB, IQueryable<TEntity> table)
   8:  {
   9:      _Table = table;
  10:      _DataContext = targetAdDB;
  11:  }

4. Let’s implement some methods in our abstract repository class:

The IsNew(..) abstract method use indicate if a given entity is a new or an existing one. Each subclass should implement this method and decides whether the entity should declare as a new entity or an existing entity. There are some other pattern to implement it, for example, declare an interface for all your entity subclasses with the a IsNew() method and each one of your subclasses will implement it. Another solution in case you have a timestamp column in your entire tables, you can create a base class which all the entity classes will inherit from it (the generated code tools support it), and implement the IsNew(…) method in the abstract class by checking if the timestamp property is equal to null.

public abstract bool IsNew(TEntity entity);

The Attach method is one of the great feature of the LINQ to SQL when you’re working with web based system. In order to update an entity there is no reason to carry all entity details through the ASP.NET lifecycle in each post back. All you have to do is it to take care only for the entity identifier/timestamp. Before you are going to perform some updates you’ll need to (1) declare a new entity class (2) set the entity identifiers (3) attach the necessary object (4) change it and then (5) save it.

   1:  public virtual void Attach(TEntity entity)
   2:  {
   3:      DataContext db = GetDataContext;
   4:      db.GetTable<TEntity>().Attach(entity, true);
   5:  }


The Save(…) methods use for saving all entities changes in our database. I declare some overloads for this method that support verity scenarios. This method check if there is a reason to attach the given entity, insert the new one or just update the current entity state in the database.

   1:  public virtual void Save(TEntity entity)
   2:  {
   3:      Save(entity, false);
   4:  }
   5:   
   6:  public virtual void Save(IEnumerable<TEntity> entities)
   7:  {
   8:      Save(entities, false);
   9:  }
  10:   
  11:  public virtual void Save(TEntity entity, bool isAlreadyAttached)
  12:  {
  13:      Save(new List<TEntity>() { entity }, isAlreadyAttached);
  14:  }
  15:   
  16:  /// <summary>
  17:  /// If the entity is a new entity insert it else update it
  18:  /// </summary>
  19:  /// <param name="entity">The modified entities</param>
  20:  /// <param name="isAlreadyAttached">False if the entity already attached in the same DataContext</param>
  21:  public virtual void Save(IEnumerable<TEntity> entities, bool isAlreadyAttached)
  22:  {
  23:      DataContext db = GetDataContext;
  24:      foreach (TEntity entity in entities)
  25:      {
  26:          if (IsNew(entity))
  27:          {
  28:              db.GetTable<TEntity>().InsertOnSubmit(entity);
  29:          }
  30:          else if (!isAlreadyAttached) 
  31:          {
  32:              db.GetTable<TEntity>().Attach(entity, true);
  33:          }
  34:      }
  35:      db.SubmitChanges();
  36:  }

The Delete(…) methods use for deleting entities from the database table. As I implemented in the save method, these methods check if the entity already attach in this specific data context and then submit the changes into the database.

   1:  public virtual void Delete(TEntity entity)
   2:  {
   3:      Delete(new List<TEntity>() { entity }, true);
   4:  }
   5:   
   6:  public virtual void Delete(TEntity entity, bool isAlreadyAttached)
   7:  {
   8:       Delete(new List<TEntity>() { entity }, isAlreadyAttached);
   9:  }
  10:   
  11:  public virtual void Delete(IEnumerable<TEntity> entities)
  12:  {
  13:      Delete(entities, true);
  14:  }
  15:   
  16:  public virtual void Delete(IEnumerable<TEntity> entities, bool isAlreadyAttached)
  17:  {
  18:      DataContext db = GetDataContext;
  19:      if (!isAlreadyAttached)
  20:      {
  21:          db.GetTable<TEntity>().AttachAll(entities, true);
  22:      }
  23:      db.GetTable<TEntity>().DeleteAllOnSubmit(entities);
  24:      db.SubmitChanges();
  25:  }


The Get(…) method uses for query the entire table.

   1:  public virtual IEnumerable<TEntity> Get()
   2:  {
   3:      return
   4:          from x in Table
   5:          select x;
   6:  }

The Disposable(…) method uses for disposing the data context on the same time that the repository dispose method is invoke. In order to use the Disposable pattern we should implement the IDisposable interface. Then when the dispose event has been raised we need to handle the data context disposable too.

   1:  #region IDisposable Members
   2:   
   3:  public void Dispose()
   4:  {
   5:      _DataContext.Dispose();
   6:  }
   7:   
   8:  #endregion

This was the entire repository abstract class. In the next step we’ll create the concrete entity classes which inherit from it.

5. Creating the entity repository class

In order to create our custom entity repository class we need to inherit from the abstract repository class which we defined above. We transfer two generics parameters to our base repository class, the first one define the type of the entity which we want to work with, in this particular repository, and the second one is the type of our custom data context.

   1:  public class CustomerRepository : Repository<Customer, TargetAdDB>
   2:  {
   3:      public CustomerRepository()
   4:      { }
   5:   
   6:      public CustomerRepository (TargetAdDB targetAdDB, IQueryable<Customer> customers) 
   7:          :base(targetAdDB, customers)
   8:      { }
   9:  }

As you can see, this repository class implements the IsNew(…) method and will return true in case the entity id is equal to default integer (zero).

   1:  public override bool IsNew(Customer entity)
   2:  {
   3:      return (entity.CustomerId == default(int));
   4:  }

I also added some wrapper for the entity table class.

   1:  public virtual IQueryable<Customer> Customers
   2:  {
   3:      get
   4:      {
   5:          return Table;
   6:      }
   7:  }

And of course, we also have the basic Get(byId) method in order to retrieve a specific entity from the database by its id.

   1:  public virtual Customer Get(int customerId)
   2:  {
   3:      return Customers.Single(x => x.CustomerId == customerId);
   4:  }

6. The next section will talk about implementing some unit tests to our repository class. For unit tests I used a test project with visual studio tests and for the Mock Object framework I choose the great Rhino Mock framework.

I divide the LINQ to SQL test to two parts: Queries and Entities Changes. The goal of the first one is to tests the queries against the data source, and the second will test the database manipulation (DML) of our entities.

I created a test class with some initial declarations and some helper methods which can help us to check the test result.

   1:  [TestClass()]
   2:  public class CustomerRepositoryTest
   3:  {
   4:      public CustomerRepositoryTest()
   5:      {
   6:          _Mock = new MockRepository();
   7:          _LinqDB = _Mock.CreateMock<LinqDB>();
   8:      }
   9:   
  10:      private CustomerRepository _CustomerRepository;
  11:      private LinqDB _LinqDB;
  12:      private MockRepository _Mock;
  13:   
  14:      private Customer GetCustomer(bool isNew)
  15:      {
  16:          return new Customer()
  17:          {
  18:              FirstName = "David",
  19:              LastName = "Leterman",
  20:              Birthday = Convert.ToDateTime("1961-11-20"),
  21:              Version =isNew?null:new Binary(new byte[]{9}),
  22:              CustomerId = isNew ? default(int): 9 
  23:          };
  24:      }
  25:   
  26:      private Order GetOrder(bool isNew, int customerId)
  27:      {
  28:          return new Order()
  29:          {
  30:              TotalPrice = 500,
  31:              CustomerId = customerId
  32:          };
  33:      }
  34:   
  35:      private bool AreEquals(Customer custumer1, Customer custumer2)
  36:      {
  37:          return ((custumer1.CustomerId == custumer2.CustomerId) 
  38:              && (custumer1.FirstName == custumer2.FirstName)
  39:              && (custumer1.LastName == custumer2.LastName)
  40:              && (custumer1.Birthday.CompareTo(custumer2.Birthday) == 0));
  41:      }
  42:   
  43:      private bool AreEquals(Order order1, Order order2)
  44:      {
  45:          return ((order1.CustomerId == order2.CustomerId)
  46:            && (order1.TotalPrice == order2.TotalPrice));
  47:      }
  48:  }

Testing the Entities Changes:

In this section I’ll show you how to test each one of my changes method (Save and Delete):

First, I'll test the save mothod. I created a new customer and associated a new order to him. Then I checked the ChangeSet collection of the data context class to see if the updates have been affected.

   1:  [TestMethod]
   2:  public void Save_NewCustomerWithoutOrders_Succeed()
   3:  {
   4:      _CustomerRepository = _Mock.PartialMock<CustomerRepository>(_LinqDB, 
   5:          new List<Customer>().AsQueryable());
   6:      Customer customer = GetCustomer(true);
   7:      using (_Mock.Record())
   8:      {
   9:          _LinqDB.SubmitChanges();
  10:      }
  11:   
  12:      using (_Mock.Playback())
  13:      {
  14:          _CustomerRepository.Save(customer);
  15:      }
  16:      ChangeSet changeSet = _LinqDB.GetChangeSet();
  17:      List<Customer> insertedCustomers = new
  18:      List<Customer>(changeSet.Inserts.OfType<Customer>());
  19:      Assert.IsTrue(AreEquals(customer, insertedCustomers[0]));
  20:  }

Next, I'll test the delete mothod. The concept for this test is very similar to the former test. I performed the deletion and then I checked the ChangeSet deletes collection.

   1:  [TestMethod]
   2:  public void Delete_ExistingCustomer_Success()
   3:  {
   4:      Customer customer = GetCustomer(false);
   5:      List<Customer> Customers = new List<Customer>();
   6:      Customers.Add(customer);
   7:      _CustomerRepository = _Mock.PartialMock<CustomerRepository>(_LinqDB, new List<Customer>().AsQueryable());
   8:   
   9:      using (_Mock.Record())
  10:      {
  11:          _LinqDB.SubmitChanges();
  12:      }
  13:   
  14:      using (_Mock.Playback())
  15:      {
  16:          _CustomerRepository.Delete(customer, false);
  17:      }
  18:      ChangeSet changeSet = _LinqDB.GetChangeSet();
  19:      List<Customer> deletesCustomers = new List<Customer>(changeSet.Deletes.OfType<Customer>());
  20:      Assert.IsTrue(AreEquals(customer, deletesCustomers[0]));
  21:  }

Testing the Queries:

In this section I’ll show you how to test some queries. The test has been executed against in memory table state with predefine data. The last part is to compare the retrieved information to the expected results.

   1:  [TestMethod]
   2:  public void Get_ExistingCustomer_Success()
   3:  {
   4:      List<Customer> customers = new List<Customer>();
   5:      Customer customer1 = GetCustomer(false);
   6:      customers.Add(customer1);
   7:      Customer customer2 = GetCustomer(false);
   8:      customers.Add(customer2);
   9:      Customer customer3 = GetCustomer(false);
  10:      customers.Add(customer3);
  11:      _CustomerRepository = new CustomerRepository(_LinqDB, customers.AsQueryable());
  12:      Customer existedCustomer = _CustomerRepository.Get(customer2.CustomerId);
  13:      Assert.IsTrue(AreEquals(customer2, existedCustomer));
  14:  }

Summary

In this post I demonstrated my solution for implementing a testable pattern to LINQ to Sql. You can look at the entire source code and a lot of more examples in the attachment. In my next posts I’ll write about some best practices when working with the LINQ to Sql framework.

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים