Active Record "Mock" Framework

kick it on DotNetKicks.com
Recently, I have been working on my Castle ActiveRecord talk. I am writing some WCF services that will be consumed by a Silverlight client. Because I write my code using TDD, I needed to be able to write tests against the WCF services. I wanted to mock out the backing store for my objects, but with ActiveRecord and NHibernate, this is not so straight-forward. Instead, I came across a post about mocking the backing store for NHibernate tests.

The idea is that you use SQLite in "memory-mode" as your database. This makes it so the database vanishes as soon as your connection is destroyed. This is great for testing! No database server… no database file… pure database interfaces. It is not a mock in the traditional sense of the word, but it lets me write tests as if I had a mock framework.

So, I modified the code to work for ActiveRecord. Note that I am overriding the DriverConnectionProvider to keep the connection alive. This is because the in-memory database gets destroyed when the connection closes.

If you want to use this class, add the following class to your test project. You will need to download and add System.Data.SQLite as a project reference.

   1: namespace ActiveRecordTestHelper
   2: {
   3:  public class ActiveRecordMockConnectionProvider : NHibernate.Connection.DriverConnectionProvider
   4:  {
   5:      private static IDbConnection _connection;
   6:  
   7:      public override IDbConnection GetConnection()
   8:      {
   9:          if (_connection == null)
  10:              _connection = base.GetConnection();
  11:          return _connection;
  12:      }
  13:  
  14:      public override void CloseConnection(IDbConnection conn)
  15:      {
  16:      }
  17:  
  18:      /// <summary>
  19:      /// Destroys the connection that is kept open in order to keep the in-memory database alive.  Destroying
  20:      /// the connection will destroy all of the data stored in the mock database.  Call this method when the
  21:      /// test is complete.
  22:      /// </summary>
  23:      public static void ExplicitlyDestroyConnection()
  24:      {
  25:          if (_connection != null)
  26:          {
  27:              _connection.Close();
  28:              _connection = null;
  29:          }
  30:      }
  31:  
  32:      /// <summary>
  33:      /// Initializes ActiveRecord and the Database that ActiveRecord uses to store the data.  Call this method
  34:      /// before the test executes.
  35:      /// </summary>
  36:      /// <param name="types">A list of ActiveRecord types that will be created in the database</param>
  37:      public static void InitializeActiveRecord(params Type[] types)
  38:      {
  39:          ActiveRecordStarter.ResetInitializationFlag();
  40:          ActiveRecordStarter.Initialize(GetMockConfiguration(), types);
  41:          ActiveRecordStarter.CreateSchema();
  42:      }
  43:  
  44:      private static IConfigurationSource GetMockConfiguration()
  45:      {
  46:          var properties = new Hashtable{
  47:                                  {"hibernate.connection.driver_class", "NHibernate.Driver.SQLite20Driver"},
  48:                                  {"hibernate.dialect", "NHibernate.Dialect.SQLiteDialect"},
  49:                                  {"hibernate.connection.provider", ConnectionProviderLocator},
  50:                                  {"hibernate.connection.connection_string", "Data Source=:memory:;Version=3;New=True;"}};
  51:  
  52:          var source = new InPlaceConfigurationSource();
  53:          source.Add(typeof(ActiveRecordBase), properties);
  54:          return source;
  55:      }
  56:  
  57:      private static string ConnectionProviderLocator
  58:      {
  59:          get { return String.Format("{0}, {1}", TypeOfEnclosingClass.FullName, EnclosingAssemblyName.Split(',')[0]); }
  60:      }
  61:  
  62:      private static Type TypeOfEnclosingClass
  63:      {
  64:          get { return MethodInfo.GetCurrentMethod().DeclaringType; }
  65:      }
  66:  
  67:      private static string EnclosingAssemblyName
  68:      {
  69:          get { return Assembly.GetAssembly(TypeOfEnclosingClass).FullName; }
  70:      }
  71:  }
  72: }

By using this class, you simply need to initialize ActiveRecord in your test StartUp method, and destroy the connection in the test TearDown Method. Here is an example of a test fixture that takes advantage of my class:

   1: [TestFixture]
   2: public class IngredientServiceTests
   3: {
   4:   private IRecipeBoxService _recipeBoxService;
   5:  
   6:   [SetUp]
   7:   public void SetUp()
   8:   {
   9:       ActiveRecordMockConnectionProvider.InitializeActiveRecord(typeof(Ingredient));
  10:       _recipeBoxService = new RecipeBoxService();
  11:   }
  12:  
  13:   [TearDown]
  14:   public void TearDown()
  15:   {
  16:       ActiveRecordMockConnectionProvider.ExplicitlyDestroyConnection();
  17:   }
  18:  
  19:   [Test]
  20:   public void Test_Get_Ingredients()
  21:   {
  22:       Ingredient mockIngredient = new Ingredient("ABCD", "DEF");
  23:       mockIngredient.Save();
  24:  
  25:       IList<Ingredient> ingredients = new List<Ingredient>(_recipeBoxService.AllIngredients());
  26:  
  27:       Assert.That(ingredients.Count, Is.EqualTo(1));
  28:       Assert.That(ingredients[0].Name, Is.EqualTo("ABCD"));
  29:       Assert.That(ingredients[0].Description, Is.EqualTo("DEF"));
  30:   }
  31: }


                            
                                                                    
                            
            
                        

Leave a Reply