Jay Harris is Cpt. LoadTest

a .net developers blog on improving user experience of humans and coders
Home | About | Speaking | Contact | Archives | RSS
 
Filed under: Programming | Testing | Tools

Recently, I was writing unit tests for a web application built on Castle ActiveRecord. My goal was to mock ActiveRecord's data store, rather than use a Microsoft SQL Server database for testing. SQL Server backing just would not fit my needs, where a mock data store would serve much better:

  • I did not want a SQL Server installation to be a requirement for me, the other developers, and my Continuous Integration server.
  • I wanted something fast. I didn't want to have to wait for SQL Server to build / tear down my schema.
  • I wanted something isolated, so the other developers, and my CI server, and I wouldn't have contention over the same database, but didn't want to have to deal with independent SQL Server instances for everyone.

Essentially what I wanted was a local, in-memory database that could be quickly initialized and destroyed specifically for my tests. The resolution was using SQLite for ADO.Net, using an in-memory SQLite instance. Brian Genisio has a fantastic write-up on mocking the data store for Castle ActiveRecord using this SQLite for ADO.Net. The post made my day, since I was looking for a way to do this, and he had already done all of the work <grin/>. I encourage you to read his post first, as the rest of this post assumes you have already done so.

Brian's post was a great help to me; I made a few enhancements to what he started to make it fit my needs even more.

My updated version of Brian's ActiveRecordMockConnectionProvider class:

using System;
using System.Collections;
using System.Data;
using System.Reflection;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Castle.ActiveRecord.Framework.Config;
using NHibernate.Connection;

namespace ActiveRecordTestHelper
{
  public class ActiveRecordMockConnectionProvider : DriverConnectionProvider
  {
    private static IDbConnection _connection;

    private static IConfigurationSource MockConfiguration
    {
      get
      {
        var properties = new Hashtable
            {
              {"hibernate.connection.driver_class",
                "NHibernate.Driver.SQLite20Driver"},
              {"hibernate.dialect", "NHibernate.Dialect.SQLiteDialect"},
              {"hibernate.connection.provider", ConnectionProviderLocator},
              {"hibernate.connection.connection_string",
                "Data Source=:memory:;Version=3;New=True;"}
            };

        var source = new InPlaceConfigurationSource();
        source.Add(typeof (ActiveRecordBase), properties);

        return source;
      }
    }

    private static string ConnectionProviderLocator
    {
      get { return String.Format("{0}, {1}", TypeOfEnclosingClass.FullName,
                                    EnclosingAssemblyName.Split(',')[0]); }
    }

    private static Type TypeOfEnclosingClass
    {
      get { return MethodBase.GetCurrentMethod().DeclaringType; }
    }

    private static string EnclosingAssemblyName
    {
      get { return Assembly.GetAssembly(TypeOfEnclosingClass).FullName; }
    }

    public override IDbConnection GetConnection()
    {
      if (_connection == null)
        _connection = base.GetConnection();

      return _connection;
    }

    public override void CloseConnection(IDbConnection conn) {}

    /// <summary>
    /// Destroys the connection that is kept open in order to keep the
    /// in-memory database alive. Destroying the connection will destroy
    /// all of the data stored in the mock database. Call this method when
    /// the test is complete.
    /// </summary>
    public static void ExplicitlyDestroyConnection()
    {
      if (_connection != null)
      {
        _connection.Close();
        _connection = null;
      }
    }

    /// <summary>
    /// Initializes ActiveRecord and the Database that ActiveRecord uses to
    /// store the data. Call this method before the test executes.
    /// </summary>
    /// <param name="useDynamicConfiguration">
    /// Use reflection to build configuration, rather than the Configuration
    /// file.
    /// </param>
    /// <param name="types">
    /// A list of ActiveRecord types that will be created in the database
    /// </param>
    public static void InitializeActiveRecord(bool useDynamicConfiguration,
                                              params Type[] types)
    {
      ActiveRecordStarter.ResetInitializationFlag();
      IConfigurationSource configurationSource = useDynamicConfiguration
                                       ? MockConfiguration
                                       : ActiveRecordSectionHandler.Instance;
      ActiveRecordStarter.Initialize(configurationSource, types);
      ActiveRecordStarter.CreateSchema();
    }

    /// <summary>
    /// Initializes ActiveRecord and the Database that ActiveRecord uses to
    /// store the data based. Configuration is dynamically generated using
    /// reflection. Call this method before the test executes.
    /// </summary>
    /// <param name="types">
    /// A list of ActiveRecord types that will be created in the database
    /// </param>
    [Obsolete("Use InitializeActiveRecord(bool, params Type[])")]
    public static void InitializeActiveRecord(params Type[] types)
    {
      InitializeActiveRecord(true, types);
    }
  }
}

In my class I have overloaded the method InitializeActiveRecord to include the boolean parameter useDynamicConfiguration, governing if the configuration is dynamically built using Reflection or if the configuration in your app.config is used instead. If the parameter is not specified, it default to false (Use app.config).

Why? Brian's original code, as is, is meant to be dropped in as a new class within your test assembly, and uses reflection to dynamically determine the provider information, including the fully-qualified class name and assembly of the new DriverConnectionProvider. Reflection makes for little effort for me when I want to drop in the class into a new test assembly. Drop it in and go; no need to even modify the app.config. However, if I want to switch my provider back to SQL Server or some other platform, I have to modify the code and recompile.

My modifications remove the restriction of configuration in compiled code, allow configuration to be placed in app.config, while preserving the existing functionality for backward compatibility. By allowing app.config-based configuration, users can quickly switch back-and-forth between SQLite and SQL Server databases without having to modify and recompile the application. To use this customized ActiveRecordMockConnectionProvider class without dynamic configuration, add the following code to the configuration block of your test's app.config.

<activerecord>
  <config>
    <add key="hibernate.connection.driver_class"
      value="NHibernate.Driver.SQLite20Driver" />
    <add key="hibernate.dialect" value="NHibernate.Dialect.SQLiteDialect" />
    <add key="hibernate.connection.provider"
      value="ActiveRecordTestHelper.ActiveRecordMockConnectionProvider, ActiveRecordTestHelper" />
    <add key="hibernate.connection.connection_string"
      value="Data Source=:memory:;Version=3;New=True;" />
  </config>
</activerecord>

The catch is that you will need to know the fully-qualified class and assembly information for your provider (Line 6, above). This means you will have to modify it for every test assembly. To get around this, compile the code into a separate assembly (I called mine 'ActiveRecordTestHelper.dll'), and reference this new assembly in your test assembly. By using a separate assembly, you no longer need to modify the activerecord configuration block for every instance, and can reuse the same block everywhere the new assembly is referenced.

And to switch over from in-memory SQLite to SQL Server, just comment out the SQLite block and uncomment the SQL Server block (or whatever other provider you are using).

Download: ActiveRecordMockConnectionProvider.zip
Includes:

  • Source code for the ActiveRecordMockConnectionProvider class
  • Sample Class that uses the new provider
  • Sample app.config containing the ActiveRecord block using the provider.
  • Compiled versions of ActiveRecordTestHelper.dll

As always, this code is provided with no warranties or guarantees. Use at your own risk. Your mileage may vary.
And thanks again to Brian Genisio.

Thursday, October 30, 2008 9:10:01 AM (Eastern Standard Time, UTC-05:00)  #    Comments [1] - Trackback

Filed under: Events
Interested in the Ray Ozzie keynotes next week, but can't make it to PDC? Come watch the two keynotes (Monday and Tuesday) at SRT Solutions in Ann Arbor. This community event will include remote viewing of the keynotes and discussion about the news. Lunch will be provided, as our local Microsoft office is sponsoring the event for both days. (Thank you, Microsoft!)
Remote Viewing & Discussion of the Ray Ozzie Keynotes
Monday, October 27, 11:30a-2:00p - Register
Tuesday, October 28, 11:30a-2:00p - Register
SRT Solutions
206 S. Fifth Avenue, Suite 200
Ann Arbor, Michigan 48104
Come out for the show. Space is limited, so please register. (Note: if you wish to attend both days, you must register for each day separately.)
I look forward to seeing you there.
Monday, October 20, 2008 12:33:29 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - Trackback

Filed under: Continuous Integration | Events | Speaking
Tomorrow night, Wednesday, 08 October, I will be speaking at the Ann Arbor Dot Net Developers meeting. We will be discussing Continuous Integration, focusing on CI as a process, not just a toolset. Come out to Ann Arbor, enjoy some pizza, and hear about what Continuous Integration can do for your development cycle.
Continuous Integration: It's more than just a toolset
Wednesday, 08 October, 2008 @ 6:00pm
SRT Solutions
206 South Fifth Ave, Suite 200
Ann Arbor, MI 48104

Session Abstract:

Does your team spend days integrating code at the end of a project? Continuous Integration can help. Using Continuous Integration will eliminate that end-of-project integration stress, and at the same time will make your development process easier. But Continuous Integration is more than just a tool like CruiseControl.Net; it is a full development process designed to bring you closer to your mainline, increase visibility of project status throughout your team, and to streamline deployments to QA or to your client. Find out what Continuous Integration is all about, and what it can do for you.
Tuesday, October 7, 2008 1:45:27 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - Trackback