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.