views:

332

answers:

2

I'm new to Moq, and just started on a project that's already in development. I'm responsible for setting up unit testing. There's a custom class for the DatabaseFactory that uses EnterpriseLibrary and looks like this:

public Database CreateCommonDatabase()
{
  return CreateDatabaseInstance(string.Empty);
}

private static Database CreateDatabaseInstance(string foo)
{
  var database = clientCode == string.Empty
      ? DatabaseFactory.CreateDatabase("COMMON")
      : new OracleDatabase(new ClientConnections().GetConnectionString(foo)));
  return database;
}

Now, here's where that gets used (ResultData is another class of the type DataSet):

public ResultData GetNotifications(string foo, string foo2, Database database)
{
    var errMsg = string.Empty;
    var retval = 0;
    var ds = new DataSet();

    var sqlClause =
    @"[Some SELECT statement here that uses foo]";

    DbCommand cm = database.GetSqlStringCommand(sqlClause);
    cm.CommandType = CommandType.Text;

    // Add Parameters
    if (userSeq != string.Empty)
    {
      database.AddInParameter(cm, ":foo2", DbType.String, foo2);
    }

    try
    {
      ds = database.ExecuteDataSet(cm);
    }
      catch (Exception ex)
    {
      retval = -99;
      errMsg = ex.Message;
    }

    return new ResultData(ds, retval, errMsg);
}

Now, originally, the Database wasn't passed in as a parameter, but the method was creating a new instance of the DatabaseFactory using the CreateCommonDatabase method, and using it from there. However, that leaves the class untestable because I can't keep it from actually hitting the database. So, I went with Dependency Injection, and pass the Database in.

Now, I'm stuck, because there's no way to mock Database in order to test GetNotifications. I'm wondering if I'm overly complicating things, or if I'm missing something. Am I doing this the right way, or should I be rethinking how I've got this set up?

Edit to add more info***

I really don't want to test the database. I want the Data.Notifications class (above) to return an instance of ResultData, but that's all I really want to test. If I go a level up, to the Business layer, I have this:

public DataSet GetNotifications(string foo, string foo1, out int returnValue, out string errorMessage, Database database)
{
    ResultData rd = new data.Notifications().GetNotifications(foo, foo1, database);

    returnValue = rd.ResultValue;
    errorMessage = rd.ErrorMessage;

    return rd.DataReturned;
}

So, originally, the database wasn't passed in, it was the Data.Notifications class that created it - but then again, if I left it that way, I couldn't help but hit the database to test this Business layer object. I modified all of the code to pass the Database in (which gets created a the web's Base page), but now I'm just not certain what to do next. I thought I was one unit test away from having this resolved, but apparently, either I'm wrong or I've got a mental roadblock to the right path.

A: 

You should be able to create a mock Database object if the methods in it are virtual. If they are not, then you have a little bit of a problem.

I don't know what type "Database" is, but you have a few options.

  1. If you own the source code to Database, I would recommend extracting an interface IDatabase, rather than dealing with a Database class type. This will eliminate some complexity and give you something extremely testable.

  2. If you don't have access to the Database class, you can always solve this with another layer of abstraction. Many people in this case use a Repository pattern that wraps the data access layer. Generally speaking in this case, most people leave testing Respository classes to integration tests (tests without any isolation), rather than unit tests.

Here's how you'd setup your test using option #1:

[TestMethod]
public void GetNotifications_PassedNullFoo_ReturnsData()
{
     //Arrange
     Mock<IDatabase> mockDB = new Mock<IDatabase>();
     mockDB.Setup(db => db.ExecuteDataSet()).Returns(new DataSet() ... );

     //Act
     FooClass target = new fooClass();
     var result = target.GetNotifications(null, "Foo2", mockDB.Object);

     //Assert
     Assert.IsTrue(result.DataSet.Rows.Count > 0);
}

My dataset code is a little rusty, but hopefully this gives you the general idea.

Anderson Imes
Database is in the MS EnterpriseLibrary namespace, so it's MS code, not my code. And the methods are static, which is why I can't mock them. I was told that we'd be using Unity in the coming months, which, if I understand correctly, would resolve the issue, but it's not an option for today.
Misty Fowler
@Misty Fowler: then you'd go with option #2 and create a repository that then utilizes those static methods, but is itself an instance class with a set of instance methods that implement a simpler interface that is easy to mock, like IFooRepository. You'll have a standard implementation of this and during a unit test you will inject your mock, rather than the real one. Once you get Unity, you'll be able to simply do container.RegisterType<IFooRespository, RealFooRepository>() and then container.Resolve<FooClass>() and it will automatically do the injection for you.
Anderson Imes
@Misty Fowler: #2 is probably going to get you *more* ready for Unity.
Anderson Imes
Anderson - yeah, I've come to the conclusion that this application requires Unity in order to move forward. I had been hoping that wasn't the case, but it is what it is, I suppose.Thanks to you and to Owen for your input - it's helped me realize what's needed.
Misty Fowler
A: 

Based on the code you've given, I would think you would want to talk to the database, and not a mocked version.

The reason is that your GetNotifications code contains DB-specific instructions, and you'll want those to pass validation at the DB Engine level. So just pass in a Database that is connected to your test DB instance.

If you took the testing abstraction to a higher level, where you built unit tests for the database call and a version of this test that used a mocked database, you'd still have to run integration tests, which ends up being triple the work for the same amount of code coverage. In my opinion, it's far more efficient to do an integration test at tier borders you control then to write unit tests for both sides of the contract and integration tests.

Owen Graupman
I can't paste the code I need to in here. So, I'm going to modify my original post.
Misty Fowler