- Posted by liammclennan on March 24, 2009
The project I am working on is an Asp.Net MVC application. I wanted to add integration tests that call either controller actions or application services and test the integrated application. To do this I needed to:
- automate the process of setting up an integration testing database environment.
- Duplicate my production IoC solution in such a way that it can be used in tests.
- Provide an easy way for integration tests to establish a context for the integration tests. This really just means simulating a logged in user.
Automate the Process of Setting Up an Integration Testing Database Environment
We manage our sql server database by keeping two sql scripts in subversion: a schema script and a test data script. Therefore to setup an integration database environment all I had to do was execute the two scripts. Here is the target I had to add to my NAnt build script:
<target name="integrationTest" depends="test">
<exec program="sqlcmd" commandline="-U ${Integration.DB.Username} -P ${Integration.DB.Password} -S localhost\sql2008 -d ${Integration.DB.Name} -b -i ${database.dir}\schema.sql" />
<exec program="sqlcmd" commandline="-U ${Integration.DB.Username} -P ${Integration.DB.Password} -S localhost\sql2008 -d ${Integration.DB.Name} -b -i ${database.dir}\data.sql" />
</target>
This target builds the integration database back to the desired baseline. The only other tweak required to the build file was to update the connection string to point to the integration database:
<xmlpoke file="${testbindebug.dir}\${testassembly.name}.config"
xpath="/configuration/connectionStrings/add[@name = 'nameofconnstring']/@connectionString"
value="Data Source=localhost\sql2008;Initial Catalog=${Integration.DB.Name};User id=${Integration.DB.Username};password=${Integration.DB.Password};" />
Duplicate the Production IoC Container Setup
Because the application uses dependency injection consistently it is too hard to perform integration tests without an IoC container. Because I already have all of my services configured in the application config file all I have to do is create a singleton that initializes and exposes the container.
public static class DI
{
public static T Resolve<T>()
{
return Container.Resolve<T>();
}
private static WindsorContainer container;
private static WindsorContainer Container
{
get
{
if (container == null) container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
return container;
}
}
}
Once this is in place I can easily get an instance of any configured service:
var userService = DI.Resolve<IUserService>();
Provide a Way to Establish a Logged In User Context
Every integration test has to be executed in the context of a user. I created a helper to make it easy to establish a user context.
public static class Context
{
public static void Login(string username)
{
var userService = DI.Resolve<IUserService>();
System.Threading.Thread.CurrentPrincipal = userService.RetrieveUserByUsername(username);
}
}
and that is all that is required. I can now run integration tests against any of my controller actions or application services. Here is an example test:
[TestFixture, Category("Integration")]
public class CustomerLogsTests
{
[SetUp]
public void Setup()
{
Context.Login("tim");
}
[Test]
public void Log()
{
var service = DI.Resolve<ICustomerLogsService>();
var config = DI.Resolve<IConfigService>();
var context = DI.Resolve<IOrganisationContext>();
Assert.IsTrue(service.Log("17:08:00",
-29.111, 152.987, 55.0, 66.0, 1, 2, 1, 0, 100000, "T", 7000, 12, 12, 12, 10, 10, 10, 9, 9, 9,
9, 8, 8, 500, 520, 500, 520, 500, 520, 0));
var vehicleService = DI.Resolve<IVehicleService>();
var logs = vehicleService.RetrieveLogs(vehicleCode, DateTime.Now.Date);
Assert.IsTrue(logs.Any(l => l.Latitude.Equals(-29.111)));
}
}
