Reputation: 982
I have the following class and methods, that will be connecting to a DB, but for testing, I do not need the real connection and would need to fake it. We're using FakeItEasy for this.:
public abstract class HandlerBase
{
public string errorMessage;
private MyActionsDataModel Action
{
get
{
if (_action == null)
{
_action = new MyActionsDataModel();
using (var connection = new SqlConnection(Constants.Connections.MyDatabase))
{
connection.Open();
using (var transaction = connection.BeginTransaction(IsolationLevel.Serializable))
{
_action.Id = connection.QuerySingle<int>("UpdateAction", transaction: transaction, commandType: CommandType.StoredProcedure, param: Action);
transaction.Commit();
}
}
}
return _action;
}
}
private MyActionsDataModel _action;
public void RecordFailure(AggregateException ex)
{
Console.WriteLine("A failure happened:");
Console.WriteLine(JsonConvert.SerializeObject(ex));
errorMessage = "Inner Exception\r\n" + ex.Message;
Action.ErrorOccurredOnUtc = DateTimeOffset.UtcNow;
Action.ErrorType = ex.GetType().FullName;
Action.ErrorMessage = errorMessage;
SaveAction();
}
private void SaveAction()
{
using (var connection = new SqlConnection(Constants.Connections.MyDatabase))
{
connection.Open();
using (var transaction = connection.BeginTransaction(IsolationLevel.Serializable))
{
connection.Execute("UpdateAction", transaction: transaction,
commandType: CommandType.StoredProcedure, param: Action);
transaction.Commit();
}
}
}
}
another class that I'll be calling in my tests:
public class MyHandlerType : HandlerBase
{
private readonly MyTracker _myTracker;
public MyHandlerType(MyTracker myTracker) : base()
{
_myTracker = myTracker;
}
}
What I want is to Fake the Action parameter and also SaveAction method.
Here is the Test I have for it, but not sure how to make the Fake part.
public class HandlerTests
{
[TestCase]
public void Test_RecordFailure()
{
var innerMessage = "Throw AppException for UnitTest.";
var parentMessage = "Throw AggregationException for UnitTest.";
var testHandler = new MyHandlerType(null);
var innerException = new ApplicationException(innerMessage);
var parentException = new AggregateException(parentMessage, innerException);
testHandler.RecordFailure(parentException);
var includeInnerMessage = testHandler.errorMessage.Contains(innerMessage);
var includeParentMessage = testHandler.errorMessage.Contains(parentMessage);
Assert.IsTrue(includeInnerMessage);
Assert.IsTrue(includeParentMessage);
}
}
Upvotes: 0
Views: 733
Reputation: 982
Here is my new code (based on the suggestion from @Nkosi) and the test part:
CODE:
public abstract class HandlerBase {
private readonly Lazy<MyActionsDataModel> model;
private readonly IDbConnectionFactory connectionFactory;
protected HandlerBase(IDbConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
model = new Lazy<MyActionsDataModel>(() => {
MyActionsDataModel action = new MyActionsDataModel();
using (DbConnection connection = this.connectionFactory.Create()) {
connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) {
action.Id = connection.QuerySingle<int>("UpdateAction",
transaction: transaction,
commandType: CommandType.StoredProcedure,
param: action);
transaction.Commit();
}
}
return action;
});
}
public string ErrorMessage;
public void RecordFailure(AggregateException ex) {
Console.WriteLine("A failure happened:");
Console.WriteLine(JsonConvert.SerializeObject(ex));
ErrorMessage = "Inner Exception\r\n" + ex.Message;
MyActionsDataModel action = model.Value;
action.ErrorOccurredOnUtc = DateTimeOffset.UtcNow;
action.ErrorType = ex.GetType().FullName;
action.ErrorMessage = ErrorMessage;
saveAction(action);
}
private void saveAction(MyActionsDataModel action) {
using (DbConnection connection = new SqlConnection(Constants.Connections.MyDatabase)) {
connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) {
connection.Execute("UpdateAction", transaction: transaction,
commandType: CommandType.StoredProcedure, param: action);
transaction.Commit();
}
}
}
}
public interface IDbConnectionFactory {
DbConnection Create();
}
// Connection Factory method
public DbConnection Create() {
DbConnection connection = new SqlConnection(Constants.Connections.MyDatabase);
return connection;
}
public class MyHandlerType : HandlerBase
{
private readonly IDbConnectionFactory _connectionFactory;
public MyHandlerType(IDbConnectionFactory connectionFactory) : base(connectionFactory)
{
_connectionFactory = connectionFactory;
}
}
TEST:
public class HandlerTests
{
protected MyHandlerType _subjectUnderTest;
protected HandlerBase.IDbConnectionFactory _fakeConnectionFactory;
[SetUp]
public void Setup()
{
_fakeConnectionFactory = A.Fake<HandlerBase.IDbConnectionFactory>();
A.CallTo(() => _fakeConnectionFactory.Create()).Returns<DbConnection>(new SqlConnection(Constants.Connections.MyDatabase));
_subjectUnderTest = new MyHandlerType(_fakeConnectionFactory);
}
[TestCase]
public void Test_RecordFailure()
{
var innerMessage = "Throw AppException for UnitTest.";
var parentMessage = "Throw AggregationException for UnitTest.";
var innerException = new ApplicationException(innerMessage);
var parentException = new AggregateException(parentMessage, innerException);
_subjectUnderTest.RecordFailure(parentException);
var includeInnerMessage = testHandler.errorMessage.Contains(innerMessage);
var includeParentMessage = testHandler.errorMessage.Contains(parentMessage);
Assert.IsTrue(includeInnerMessage);
Assert.IsTrue(includeParentMessage);
}
}
Upvotes: 0
Reputation: 247571
The current class is tightly coupled to implementation concerns that make testing it in isolation difficult.
Consider refactoring the class
public abstract class HandlerBase {
private readonly Lazy<MyActionsDataModel> model;
private readonly IDbConnectionFactory connectionFactory;
protected HandlerBase(IDbConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
model = new Lazy<MyActionsDataModel>(() => {
MyActionsDataModel action = new MyActionsDataModel();
using (DbConnection connection = this.connectionFactory.Create()) {
connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) {
action.Id = connection.QuerySingle<int>("UpdateAction",
transaction: transaction,
commandType: CommandType.StoredProcedure,
param: action);
transaction.Commit();
}
}
return action;
});
}
public string ErrorMessage;
public void RecordFailure(AggregateException ex) {
Console.WriteLine("A failure happened:");
Console.WriteLine(JsonConvert.SerializeObject(ex));
ErrorMessage = "Inner Exception\r\n" + ex.Message;
MyActionsDataModel action = model.Value;
action.ErrorOccurredOnUtc = DateTimeOffset.UtcNow;
action.ErrorType = ex.GetType().FullName;
action.ErrorMessage = ErrorMessage;
saveAction(action);
}
private void saveAction(MyActionsDataModel action) {
using (DbConnection connection = connectionFactory.Create()) {
connection.Open();
using (DbTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable)) {
connection.Execute("UpdateAction", transaction: transaction,
commandType: CommandType.StoredProcedure, param: action);
transaction.Commit();
}
}
}
}
Note the introduction of an explicit dependency
public interface IDbConnectionFactory {
DbConnection Create();
}
which can have an implementation
// Connection Factory method
public DbConnection Create() {
DbConnection connection = new SqlConnection(Constants.Connections.MyDatabase);
return connection;
}
When testing the factory can be mocked to behave as desired when the subject under test is exercised.
Upvotes: 1