Warning
There is a lot of code in this post. Nothing too crazy or fancy just a LOT of code. It's too much of a pain to create and upload a project file so I've just pasted in. If for some reason you are having trouble getting the code off the page just shoot me an email and I'll send it to you. Also, I'm using MSTest so if that bothers you then please move along.
Purpose
I've always avoided doing full integration tests because it's always such a pain to get a db setup and in working order each time you need to run the tests. To make things easier I've created a helper class to manage the setup and tear-down of databases for testing. I've included a couple examples of how I'm using it in situations that require either one database for a series of tests or one database per test. Hope this makes life a little bit easier.
On with the show
Note: The biggest assumption I'm making here is that you're using integrated security for your database connection. The nice thing about this solution is that you could easily pass in ".\" for the server name (if you are using the default Sql Server Instance) and it will work on a build server quite easily.
/* The following files will need to referenced to get the Execute script code to work:
* Microsoft.SqlServer.ConnectionInfo.dll
* Microsoft.SqlServer.Management.Sdk.Sfc.dll
* Microsoft.SqlServer.Smo.dll
*
* The should all be located in the following directory
* C:\Program Files\Microsoft SQL Server\90\SDK\Assemblies
*
* Or in the event you're on 64-bit and running Sql 2008
* C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies
*/
using Microsoft.SqlServer.Server;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
public static class DBHelper
{
public static bool CreateDatabase(string dbName, string serverName)
{
if (DatabaseExists(dbName, serverName))
DropDatabase(dbName, serverName);
var connectionString = BuildConnectionString("master", serverName);
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = string.Format("CREATE DATABASE {0}", dbName);
command.ExecuteNonQuery();
}
}
return DatabaseExists(dbName, serverName);
}
public static bool DatabaseExists(string dbName, string serverName)
{
var masterConnectionString = BuildConnectionString("master", serverName);
using (var connection = new SqlConnection(masterConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = string.Format("SELECT name FROM master..sysdatabases WHERE name = '{0}'", dbName);
using (var reader = command.ExecuteReader())
{
return reader.HasRows;
}
}
}
}
public static void ExecuteScript(string script, string dbName, string serverName)
{
if (!DatabaseExists(dbName, serverName))
CreateDatabase(dbName, serverName);
var connectionString = BuildConnectionString(dbName, serverName);
using (var connection = new SqlConnection(connectionString))
{
var server = new Server(new ServerConnection(connection));
server.ConnectionContext.ExecuteNonQuery(script);
}
}
public static void DropDatabase(string dbName, string serverName)
{
var connectionString = BuildConnectionString("master", serverName);
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
var sb = new StringBuilder();
sb.AppendFormat("ALTER DATABASE {0} SET SINGLE_USER WITH ROLLBACK IMMEDIATE{1}", dbName, Environment.NewLine);
sb.AppendFormat("DROP DATABASE {0}", dbName);
command.CommandText = sb.ToString();
command.ExecuteNonQuery();
}
}
}
public static string BuildConnectionString(string dbName, string serverName)
{
var connection = new SqlConnectionStringBuilder();
connection.DataSource = serverName;
connection.InitialCatalog = dbName;
connection.IntegratedSecurity = true;
connection.Pooling = false;
return connection.ToString();
}
}
}
Below are a few tests that demonstrate it's usage. Once thing to note is that I have a .sql file that I generated in Management Studio called "baseline.sql". This script sets up all the tables and initial data I need for running my tests. One thing to note is that at the top of the generated script file is the statement:
USE MyDatabaseName
Since we are generating a database that has a timestamp appended to its name like
DBTest060120100000 the sql file will need to be fixed. What I did was simply change the script to
USE %DBName%
and then used the string.Replace method to replace the %DBName% token with the name of the database we're currently using.
[TestClass]
public class DBHelperTests
{
#region Fields
private string _dbName;
#endregion
[TestInitialize]
public void TestSetup()
{
_dbName = string.Format("TestDB{0}", DateTime.Now.ToString("ddMMyyyymmss"));
}
private string DBName
{
get { return _dbName; }
}
[TestCleanup]
public void TestCleanup()
{
if (DBHelper.DatabaseExists(DBName, Settings.Default.DBServerName))
DBHelper.CreateDatabase(DBName, Settings.Default.DBServerName);
}
[TestMethod]
public void WhenADatabaseIsSuccessfullyCreated_ThenReturnsTrue()
{
var actual = DBHelper.CreateDatabase(DBName, Settings.Default.DBServerName);
var expected = true;
Assert.AreEqual(expected, actual);
Assert.IsTrue(DBHelper.DatabaseExists(DBName, Settings.Default.DBServerName));
}
[TestMethod]
public void WhenADatabaseAlreadyExists_ThenTheExistingDatabaseIsDroppedAndANewOneIsCreated()
{
var actual = DBHelper.CreateDatabase(DBName, Settings.Default.DBServerName);
Assert.IsTrue(actual);
Assert.IsTrue(DBHelper.DatabaseExists(DBName, Settings.Default.DBServerName));
actual = DBHelper.CreateDatabase(DBName, Settings.Default.DBServerName);
Assert.IsTrue(actual);
}
[TestMethod]
public void WhenADatabaseIsCreated_ThenReturnsTrue()
{
var actual = DBHelper.DatabaseExists("Uknown", Settings.Default.DBServerName);
var expected = false;
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void WhenADatabaseIsDropped_ThenItNoLongerExistsOnTheServer()
{
DBHelper.CreateDatabase(DBName, Settings.Default.DBServerName);
Assert.IsTrue(DBHelper.DatabaseExists(DBName, Settings.Default.DBServerName));
DBHelper.DropDatabase(DBName, Settings.Default.DBServerName);
Assert.IsFalse(DBHelper.DatabaseExists(DBName, Settings.Default.DBServerName));
}
[TestMethod]
public void WhenAScriptIsExecuted_ThenTheTablesAndDataAreInsertedProperly()
{
var script = Resources.Baseline.Replace("%DBName%", DBName);
DBHelper.ExecuteScript(script, DBName, Settings.Default.DBServerName);
var connectionString = DBHelper.BuildConnectionString(DBName, Settings.Default.DBServerName);
bool actual;
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT * FROM dbo.[User]";
using (var reader = command.ExecuteReader())
{
actual = reader.HasRows;
}
}
}
Assert.IsTrue(actual);
}
}
This next example shows how you would setup a set of tests to all use the same database.
[TestClass]
public class EntityFrameworkRepositoryTest
{
#region Fields
private static string _dbName;
private EntityFrameworkRepository _repository;
#endregion
private static string DBName { get { return _dbName; } }
private EntityFrameworkRepository Repository { get { return _repository; } }
[ClassInitialize]
public static void InitializeClass(TestContext context)
{
_dbName = string.Format("TestDB{0}", DateTime.Now.ToString("ddMMyyyymmss"));
var script = Resources.Baseline.Replace("%DBName%", DBName);
DBHelper.ExecuteScript(script, DBName, Settings.Default.DBServerName);
}
[ClassCleanup]
public static void CleanupClass()
{
if (DBHelper.DatabaseExists(DBName, Settings.Default.DBServerName))
DBHelper.DropDatabase(DBName, Settings.Default.DBServerName);
}
[TestInitialize]
public void InitializeTest()
{
var sqlConnectionString = DBHelper.BuildConnectionString(DBName, Settings.Default.DBServerName);
var entityFrameworkConnectionString = Utils.GetEntityFrameworkConnectionString("Littlefish.Data", "DataModel", sqlConnectionString);
_repository = new EntityFrameworkRepository(entityFrameworkConnectionString);
}
[TestMethod]
public void WhenRetrievingAnEntityThatExistsInTheDatabase_ThenTheEntityIsSuccessfullyRetreived()
{
var user = Repository.Get().FirstOrDefault();
Assert.IsNotNull(user);
}
}
I hope this helps make the idea of doing full integration tests or fully testing your data repository much more approachable.
