namespace ChadSoft.Testing { using System; using System.Collections.Generic; using System.Data.SqlClient; using System.IO; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; public class TestDatabase : IDisposable { private readonly string connectionString; protected internal readonly string databaseFilename; public string ConnectionString { get { return connectionString; } } public string Schema { get; set; } public string TestDataScript { get; set; } /// /// Initializes a new instance of the class. /// /// /// The filename of the database to use for testing /// /// The schema. /// The test data. public TestDatabase(string databaseFilename, string schema, string testData) { if (string.IsNullOrEmpty(databaseFilename)) throw new ArgumentNullException("databaseFilename"); this.databaseFilename = databaseFilename; connectionString = string.Format( @"Server=.\SQLEXPRESS; Integrated Security=true;AttachDbFileName={0};", Path.GetFullPath(databaseFilename)); Schema = schema; TestDataScript = testData; } public void Dispose() { DeleteDatabaseFiles(); } /// /// Initializes this instance. This will /// prepare the database from tests. /// public virtual void Initialize(bool overwriteExisting) { if (overwriteExisting && File.Exists(databaseFilename)) DeleteDatabaseFiles(); EnsureDatabaseCreated(); } /// /// Helper method to execute a query with no /// return parameters /// (or at least none that you need to access). /// /// /// Jess: This was a private helper function that /// I decided to make public to make tests easier. /// This isn't supposed to be some big DAL. /// /// /// A query script of any size.
/// Note: you can use the GO keyword to delimit /// multiple queries in the same string. These /// will be executed in individual requests. /// public virtual void ExecuteQuery(string query) { using (var conn = new SqlConnection(connectionString)) { conn.Open(); // Break out the SQL statements // terminated by the "GO" statement. // (Credit to Jon Wells: http://snurl.com/5nam1) var sqlStatements = query.Split( new[] { "GO", "GO\t", "GO\r\n" }, StringSplitOptions.RemoveEmptyEntries); // Then execute each of them individually // (using the same connection w/o closing it) foreach (var sql in sqlStatements) new SqlCommand(sql, conn).ExecuteNonQuery(); } } /// /// Recreate the test data by executing the script /// in the property. /// public void Recreate() { EnsureDatabaseCreated(); if (!string.IsNullOrEmpty(TestDataScript)) ExecuteQuery(TestDataScript); } // Create a new file-based SQLEXPRESS database // (Credit to Louis DeJardin - thanks! http://snurl.com/5nbrc) protected internal virtual void CreateDatabase() { var databaseName = Path.GetFileNameWithoutExtension(databaseFilename); using (var connection = new SqlConnection( "Data Source=.\\sqlexpress;Initial Catalog=tempdb;" + "Integrated Security=true;User Instance=True;")) { connection.Open(); using (var command = connection.CreateCommand()) { command.CommandText = "CREATE DATABASE " + databaseName + " ON PRIMARY (NAME=" + databaseName + ", FILENAME='" + databaseFilename + "')"; command.ExecuteNonQuery(); command.CommandText = "EXEC sp_detach_db '" + databaseName + "', 'true'"; command.ExecuteNonQuery(); } } // After we've created the database, initialize it with any // schema and test data scripts we've been given InitializeDatabase(); } protected virtual void DeleteDatabaseFiles() { // Allow time for connections to be released System.Threading.Thread.Sleep(1000); if (File.Exists(databaseFilename)) File.Delete(databaseFilename); var logFilename = databaseFilename.Replace(".mdf", "_log.ldf"); if (File.Exists(logFilename)) File.Delete(logFilename); } protected internal virtual void EnsureDatabaseCreated() { if (File.Exists(databaseFilename)) return; CreateDatabase(); } private void InitializeDatabase() { if (!string.IsNullOrEmpty(Schema)) ExecuteQuery(Schema); if (!string.IsNullOrEmpty(TestDataScript)) ExecuteQuery(TestDataScript); } /// /// Builds one monolithic SQL query from a collection of files. /// /// The file paths containing SQL queries /// An aggregated SQL query public static string BuildSqlFromFiles(IEnumerable filePaths) { var sql = new StringBuilder(); foreach (var path in filePaths) { sql.AppendLine(File.ReadAllText(path)); sql.AppendLine("GO"); } return sql.ToString(); } } [TestClass] public class TestDatabaseTests { readonly string databaseFilename = Path.GetFullPath("Test.mdf"); [TestMethod] public void Initialize_creates_database_file_if_none_exists() { if (File.Exists(databaseFilename)) File.Delete(databaseFilename); using (var db = new TestDatabase(databaseFilename, null, null)) { db.Initialize(false); Assert.IsTrue(File.Exists(databaseFilename)); } } [TestMethod] public void Initialize_overwrite_database_file_if_it_exists() { using (var db = new TestDatabase(databaseFilename, null, null)) { Assert.IsFalse(File.Exists(databaseFilename)); File.Create(databaseFilename).Close(); db.Initialize(true); Assert.IsTrue(File.Exists(databaseFilename)); } } [TestMethod] public void Initialize_executes_schema_if_provided() { var schema = "PRINT 'Excellent!'"; var mockDb = new Mock(databaseFilename, schema, null); mockDb.Expect(db => db.ExecuteQuery("PRINT 'Excellent!'")).Verifiable(); using (var db = mockDb.Object) { db.Initialize(true); } mockDb.VerifyAll(); } [TestMethod] public void Initialize_does_not_execute_schema_if_not_provided() { var mockDb = new Mock(databaseFilename, null, null); mockDb.Expect(db => db.ExecuteQuery(It.IsAny())) .Throws(new Exception("This shouldn't be called!")); using (var db = mockDb.Object) { db.Initialize(true); } } } }