KVN
KVN

Reputation: 982

Faking SqlConnection and embedded methods in the NUnit Test, using FakeItEasy

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

Answers (2)

KVN
KVN

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

Nkosi
Nkosi

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

Related Questions