MSTest with Moq - DAL setup

2019-07-09 07:41发布

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.

2条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-07-09 08:06

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.

查看更多
Animai°情兽
3楼-- · 2019-07-09 08:13

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.

查看更多
登录 后发表回答