Is Mocking able to replace functionality wrapped inside a method?
Asked Answered
B

4

5

I'm trying to define a way to simulate an case on accessing into a database without accessing... That's probably sounds quite crazy, but it's not.

Here is an example about a method i would like to test:

    public IDevice GetDeviceFromRepository(string name)
    {
        IDevice device = null;
        IDbConnection connection = new SqlConnection(ConnectionString);
        connection.Open();
        try
        {
            IDbCommand command = connection.CreateCommand();
            command.CommandText = string.Format("SELECT DEVICE_ID,DEVICE_NAME FROM DEVICE WHERE DEVICE_NAME='{0}'", name);
            IDataReader dataReader = command.ExecuteReader();
            if(dataReader.NextResult())
            {
                device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));
            }
        }
        finally
        {
            connection.Close();
        }
        return device;
    }

I'm pretending to mock IDataReader so i can control what's being read. Something like that (using Moq framework):

    [TestMethod()]
    public void GetDeviceFromRepositoryTest()
    {
        Mock<IDataReader> dataReaderMock = new Mock<IDataReader>();
        dataReaderMock.Setup(x => x.NextResult()).Returns(true);
        dataReaderMock.Setup(x => x.GetInt32(0)).Returns(000);
        dataReaderMock.Setup(x => x.GetString(1)).Returns("myName");
        Mock<IDbCommand> commandMock = new Mock<IDbCommand>();
        commandMock.Setup(x => x.ExecuteReader()).Returns(dataReaderMock.Object);
        Mock<RemoveDeviceManager> removeMock = new Mock<RemoveDeviceManager>();
        removeMock.Setup()
        RemoveDeviceManager target =new RemoveDeviceManager(new Device(000, "myName")); 
        string name = string.Empty; 
        IDevice expected = new Device(000, "myName"); // TODO: Initialize to an appropriate value
        IDevice actual;
        actual = target.GetDeviceFromRepository(name);
        Assert.AreEqual(expected.SerialNumber, actual.SerialNumber);
        Assert.AreEqual(expected.Name, actual.Name);
    }

My question is whether i can force method GetDeviceFromRepository to replace IDataReader by mocked one.

Buffalo answered 2/9, 2009 at 15:31 Comment(1)
The main problem here is we got some legacy code that is going to be fit into a new solution. One requirement is coverage shall be over 95% on unit testing and automate build. What i would like to do is creating some unit tests in charge of achieving this coverage without performing too much refractor and without accessing external resources (i.e. database) to perform this tests quickly and build automatically several times at day. This is the reason to follow this approach.Buffalo
P
6

Although you're currently use Moq I think that the functionality you're looking for cannot be achived without dependency injection unless you use Typemock Isolator (Disclaimer - I worked at Typemock).

Isolator has a feature called "future objects" that enable replacing a future instantiation of an object with a previously created fake object:

 // Create fake (stub/mock whateever) objects
 var fakeSqlConnection = Isolate.Fake.Instance<SqlConnection>();
 var fakeCommand = Isolate.Fake.Instance<SqlCommand>();
 Isolate.WhenCalled(() => fakeSqlConnection.CreateCommand()).WillReturn(fakeCommand);

 var fakeReader = Isolate.Fake.Instance<SqlDataReader>();
 Isolate.WhenCalled(() => fakeCommand.ExecuteReader()).WillReturn(fakeReader);

 // Next time SQLConnection is instantiated replace with our fake
 Isolate.Swap.NextInstance<SqlConnection>().With(fakeSqlConnection);
Produce answered 7/9, 2009 at 8:20 Comment(0)
M
5

I would think the problem here is your direct dependency ultimately to SqlConnection. If you would use some variant of dependency injection such that your code gets access to the IDbCommand without knowing how it gets constructed, you would be able to inject your mock without much of a hassle.

I understand this doesn't quite answer your question, but in the long run doing stuff as described will give you far better testability.

Magdau answered 2/9, 2009 at 15:36 Comment(0)
A
5

I agree with Frank's answer that moving toward Dependency Injection is the better long-term solution, but there are some intermediate steps you can take to move you in that direction without biting the whole thing off.

One thing is to move the construction of the IDbConnection class into a protected virtual method inside your class:

protected virtual IDbConnection CreateConnection()
{
    return new SqlConnection(ConnectionString);
}

Then, you can create a testing version of your class like so:

public class TestingRemoteDeviceManager : RemoteDeviceManager
{
   public override IDbConnection CreateConnection()
   {
         IDbConnection conn = new Mock<IDbConnection>();
         //mock out the rest of the interface, as well as the IDbCommand and
         //IDataReader interfaces
         return conn;
   }
}

That returns a Mock or fake IDbConnection instead of the concrete SqlConnection. That fake can then return a fake IDbCommand object, which can then return a fake IDataReader object.

Ampereturn answered 2/9, 2009 at 15:50 Comment(0)
N
5

The mantra is test until fear is transformed in boredom. I think you've crossed that line here. If you would take control of the data reader, then the only code you're testing is this:

device = new Device(dataReader.GetInt32(0),dataReader.GetString(1));

There is almost nothing to test here, which is good: the data layer should be simple and stupid. So don't try to unit test your data layer. If you feel you must test it, then integration-test it against a real database.

Of course, hiding your data layer behind a IDeviceRepository interface so that you can easily mock it in order to test other code is still a good idea.

Nictitate answered 2/9, 2009 at 20:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.