So I created the Database batching code that I spoke about recently and the way I went about doing it was to create the unit tests first. And then get the code to work. My goal from the beginning was 100% code coverage of all my unit tests. So using NCover and NUnit and NMock I was able to achieve that. Then when I replaced the existing database access code with my new code. ALL the preexisting DB access unit tests passed (we have some unit tests that work with the database) in one try! WOOOTT!!! And at the same time a coworker of mine changed some of his code, didn’t even run the existing unit tests and checked his code in and he broke the build for 5 hrs. I had to wait that 5 hours before I could check my code in.
Here are my unit tests, the class in question that I am testing is the database executor class (called Database in this version). If you are interested in seeing the DB execution implementation post here and I’ll put it up in my next post. (I’d like to see if people are actually interested in this series of articles
)
[TestFixture]
public class DatabaseAccessingTestFixture
{
private List myConnectionStrings;
[SetUp]
protected virtual void SetUp()
{
myConnectionStrings = new List();
myConnectionStrings.Add("Blah!");
}
[Test]
public void SingletonAccessShouldReturnDatabaseObject()
{
Single.Instance = null;
Assert.IsInstanceOfType(typeof(Database), Database.Instance);
}
[Test]
public void ExecuteSingleCommandShouldOpenANewConnectionAndExecuteTheCommand()
{
Mockery mocks = new Mockery();
IDbCommand cmd = mocks.NewMock();
IDbConnection con = mocks.NewMock();
IDataReader rdr = mocks.NewMock();
Database db = new Database();
int workCalled = 0;
DataReaderConsumer work = delegate(IDataReader reader) { workCalled++;
reader.Read(); };
Expect.Once.On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
Expect.Once.On(con).Method("Open");
Expect.Once.On(cmd).Method("ExecuteReader").WithNoArguments().Will(Return.Value(rdr));
Expect.Once.On(rdr).Method("Close");
Expect.Once.On(cmd).SetProperty("Connection");
Expect.Once.On(con).Method("Dispose");
Expect.Once.On(rdr).Method("Read").Will(Return.Value(true));
using (CreatorScope scope = new CreatorScope(delegate { return con;}))
{
db.ExecuteSingleCommand(cmd, work, myConnectionStrings, IsolationLevel.ReadCommitted, null);
}
Assert.IsTrue(workCalled==1);
mocks.VerifyAllExpectationsHaveBeenMet();
}
[Test]
public void ExecutingABatchShouldInvokeWork2Times()
{
Mockery mocks = new Mockery();
IDbCommand cmd = mocks.NewMock();
IDbConnection con = mocks.NewMock();
IDataReader rdr = mocks.NewMock();
Database db = new Database();
int workCalled = 0;
DataReaderConsumer work = delegate(IDataReader reader)
{
workCalled++;
reader.Read();
};
using (mocks.Ordered)
{
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
//Open connection once.
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//ExecuteReader for every "singlecommand" in the batch
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(Return.Value(rdr));
Expect.Exactly(1).On(rdr).Method("Read").Will(Return.Value(true));
//Close reader when done
Expect.Exactly(1).On(rdr).Method("Close");
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Open));
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//ExecuteReader for every "singlecommand" in the batch
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(Return.Value(rdr));
Expect.Exactly(1).On(rdr).Method("Read").Will(Return.Value(true));
//Close reader when done
Expect.Exactly(1).On(rdr).Method("Close");
Expect.Once.On(con).Method("Dispose");
}
using (CreatorScope scope = new CreatorScope(delegate { return con; }))
{
db.Execute(IsolationLevel.ReadCommitted, null, myConnectionStrings,
delegate
{
db.ExecuteSingleCommand(cmd, work, myConnectionStrings, IsolationLevel.ReadCommitted, null);
db.ExecuteSingleCommand(cmd, work, myConnectionStrings, IsolationLevel.ReadCommitted, null);
});
}
Assert.IsTrue(workCalled == 2);
mocks.VerifyAllExpectationsHaveBeenMet();
}
[Test]
[ExpectedException(typeof(OurException))]
public void AttemptingABatchWithIsolationOfNoneShouldThrowOurException()
{
Database db = new Database();
db.Execute(IsolationLevel.None, null, myConnectionStrings, null);
}
[Test]
[ExpectedException(typeof (OurException))]
public void AttemptingABatchWithNullWorkParameterShouldThrowOurException()
{
Database db = new Database();
db.Execute(IsolationLevel.ReadCommitted, null, myConnectionStrings, null);
}
[Test]
public void IfStatementRequiresNewTransactionDuringABatchWeShouldThrowOurException()
{
Mockery mocks = new Mockery();
IDbCommand cmd = mocks.NewMock();
IDbConnection con = mocks.NewMock();
IDataReader rdr = mocks.NewMock();
Database db = new Database();
int workCalled = 0;
DataReaderConsumer work = delegate(IDataReader reader)
{
workCalled++;
reader.Read();
};
using (mocks.Ordered)
{
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
//Open connection once.
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//ExecuteReader for every "singlecommand" in the batch
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(Return.Value(rdr));
Expect.Exactly(1).On(rdr).Method("Read").Will(Return.Value(true));
//Close reader when done
Expect.Exactly(1).On(rdr).Method("Close");
//clean up connection on the way out.
Expect.Once.On(con).Method("Dispose");
}
bool caughtException = false;
try
{
using (CreatorScope scope = new CreatorScope(delegate { return con; }))
{
db.Execute(IsolationLevel.ReadCommitted, null, myConnectionStrings,
delegate
{
db.ExecuteSingleCommand(cmd, work, myConnectionStrings, IsolationLevel.ReadCommitted, null);
db.ExecuteSingleCommand(cmd, work, myConnectionStrings, IsolationLevel.ReadCommitted, null);
});
}
}
catch (OurException e)
{
caughtException = true;
}
Assert.IsTrue(caughtException);
mocks.VerifyAllExpectationsHaveBeenMet();
}
[Test]
public void IfStatementHasIncompatibleIsolationTypeDuringABatchWeShouldThrowOurException()
{
Mockery mocks = new Mockery();
IDbCommand cmd = mocks.NewMock();
IDbConnection con = mocks.NewMock();
IDataReader rdr = mocks.NewMock();
Database db = new Database();
int workCalled = 0;
DataReaderConsumer work = delegate(IDataReader reader)
{
workCalled++;
reader.Read();
};
using (mocks.Ordered)
{
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
//Open connection once.
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//ExecuteReader for every "singlecommand" in the batch
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(Return.Value(rdr));
Expect.Exactly(1).On(rdr).Method("Read").Will(Return.Value(true));
//Close reader when done
Expect.Exactly(1).On(rdr).Method("Close");
//clean up connection on the way out.
Expect.Once.On(con).Method("Dispose");
}
bool caughtException = false;
try
{
using (CreatorScope scope = new CreatorScope(delegate { return con; }))
{
db.Execute(IsolationLevel.ReadCommitted, null, myConnectionStrings,
delegate
{
db.ExecuteSingleCommand(cmd, work, myConnectionStrings,IsolationLevel.ReadCommitted, null);
db.ExecuteSingleCommand(cmd, work, myConnectionStrings,IsolationLevel.Snapshot, null);
});
}
}
catch (OurException e)
{
caughtException = true;
}
Assert.IsTrue(caughtException);
mocks.VerifyAllExpectationsHaveBeenMet();
}
[Test]
public void ExecuteSingleCommandWithSnapshotIsolationShouldExecuteUnderSnapshotIsolation()
{
Mockery mocks = new Mockery();
IDbCommand cmd = mocks.NewMock();
IDbConnection con = mocks.NewMock();
IDataReader rdr = mocks.NewMock();
Database db = new Database();
int workCalled = 0;
DataReaderConsumer work = delegate(IDataReader reader)
{
workCalled++;
reader.Read();
};
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
Expect.Once.On(con).Method("Open");
Expect.Once.On(cmd).Method("ExecuteReader").WithNoArguments().Will(Return.Value(rdr));
Expect.Once.On(rdr).Method("Close");
Expect.Once.On(cmd).SetProperty("Connection");
Expect.Once.On(con).Method("Dispose");
Expect.Once.On(rdr).Method("Read").Will(Return.Value(true));
using (CreatorScope scope = new CreatorScope(delegate { return con; }))
{
db.ExecuteSingleCommand(cmd, work, myConnectionStrings, IsolationLevel.Snapshot, new TimeSpan(20000));
}
Assert.IsTrue(workCalled == 1);
mocks.VerifyAllExpectationsHaveBeenMet();
}
[Test]
public void StatementFailsIfMirroringServerFailsOverwithSQLAndTransactionException()
{
Mockery mocks = new Mockery();
IDbCommand cmd = mocks.NewMock();
IDbConnection con = mocks.NewMock();
IDataReader rdr = mocks.NewMock();
Database db = new Database(2, 50);
int workCalled = 0;
DataReaderConsumer work = delegate(IDataReader reader)
{
workCalled++;
reader.Read();
};
using (mocks.Ordered)
{
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
//Open connection once.
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//retry command for each retry.
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(
Throw.Exception(SqlExceptionCreator.CreateSqlException("Blah", 8506,20)));
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//retry command for each retry.
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(
Throw.Exception(new TransactionInDoubtException("Transaction ambiguous")));
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//retry command for each retry.
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(
Throw.Exception(SqlExceptionCreator.CreateSqlException("Blah", 8506,20)));
Expect.Once.On(con).Method("Dispose");
}
bool exceptionThrown = false;
try
{
using (CreatorScope scope = new CreatorScope(delegate { return con; }))
{
db.ExecuteSingleCommand(cmd, work, myConnectionStrings, IsolationLevel.ReadCommitted, null);
}
}
catch (OurException e)
{
Assert.AreEqual(8506, ((SqlException)e.InnerException).Number);
exceptionThrown = true;
}
Assert.IsTrue(exceptionThrown);
Assert.IsTrue(workCalled == 0);
mocks.VerifyAllExpectationsHaveBeenMet();
}
[Test]
public void StatementShouldThrowExceptionIfExecuteReaderThrowsExceptionWithErrorClassLessThan20()
{
Mockery mocks = new Mockery();
IDbCommand cmd = mocks.NewMock();
IDbConnection con = mocks.NewMock();
IDataReader rdr = mocks.NewMock();
Database db = new Database(2, 50);
int workCalled = 0;
DataReaderConsumer work = delegate(IDataReader reader)
{
workCalled++;
reader.Read();
};
using (mocks.Ordered)
{
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
//Open connection once.
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//retry command for each retry.
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(
Throw.Exception(SqlExceptionCreator.CreateSqlException("Blah", 22, 10)));
Expect.Once.On(con).Method("Dispose");
}
bool exceptionThrown = false;
try
{
using (CreatorScope scope = new CreatorScope(delegate { return con; }))
{
db.ExecuteSingleCommand(cmd, work, myConnectionStrings,IsolationLevel.ReadCommitted, null);
}
}
catch (OurException e)
{
Assert.AreEqual(22, ((SqlException)e.InnerException).Number);
exceptionThrown = true;
}
Assert.IsTrue(exceptionThrown);
Assert.IsTrue(workCalled == 0);
mocks.VerifyAllExpectationsHaveBeenMet();
}
[Test]
public void SingleStatementShouldSucceedOnDualRailFailover()
{
Mockery mocks = new Mockery();
IDbCommand cmd = mocks.NewMock();
IDbConnection con = mocks.NewMock();
IDataReader rdr = mocks.NewMock();
Database db = new Database(2, 50);
int workCalled = 0;
DataReaderConsumer work = delegate(IDataReader reader)
{
workCalled++;
reader.Read();
};
myConnectionStrings.Add("dualRail");
using (mocks.Ordered)
{
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
//Open connection once.
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//retry command for each retry.
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(
Throw.Exception(SqlExceptionCreator.CreateSqlException("Blah", 8506, 20)));
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//retry command for each retry.
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(
Throw.Exception(SqlExceptionCreator.CreateSqlException("Blah", 8506, 20)));
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//retry command for each retry.
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(
Throw.Exception(SqlExceptionCreator.CreateSqlException("Blah", 8506, 20)));
Expect.Once.On(con).Method("Dispose");
Expect.Exactly(1).On(con).GetProperty("State").Will(Return.Value(ConnectionState.Closed));
//Open connection once.
Expect.Once.On(con).Method("Open");
Expect.Exactly(1).On(cmd).SetProperty("Connection");
//retry command for each retry.
Expect.Exactly(1).On(cmd).Method("ExecuteReader").WithNoArguments().Will(Return.Value(rdr));
Expect.Once.On(rdr).Method("Read").Will(Return.Value(true));
Expect.Once.On(rdr).Method("Close");
Expect.Once.On(con).Method("Dispose");
}
bool exceptionThrown = false;
try
{
using (CreatorScope scope = new CreatorScope(delegate { return con; }))
{
db.ExecuteSingleCommand(cmd, work, myConnectionStrings, IsolationLevel.ReadCommitted, null);
}
}
catch (OurException e)
{
Assert.AreEqual(8506, ((SqlException)e.InnerException).Number);
exceptionThrown = true;
}
Assert.IsFalse(exceptionThrown);
Assert.IsTrue(workCalled == 1);
mocks.VerifyAllExpectationsHaveBeenMet();
}
}