如何测试依赖于数据库内容的方法?

时间:2016-03-12 12:06:46

标签: c# entity-framework unit-testing mocking

我们有返回匹配数据库条目的方法,或者如果不存在匹配则创建条目并返回该条目。使用实体框架。

public Transaction FindOrCreateTransactionByID(string id, DBContext db)
{
    Transaction t = db.Transactions.SingleOrDefault(f => f.TransactionID == id);
    if(t == null)
    {
        t = new Transaction { TransactionID = id };
        db.Transactions.Add(t);
        db.SaveChanges();
    }
    return t;
}

除了上面的方法还有更多的方法,但它应该说明这种情况。

我们应该试着嘲笑DBContext吗?通过DbSet[Transactions]然后嘲笑那个?将方法分解为Find()Create()而不是?

2 个答案:

答案 0 :(得分:0)

我尝试在本地复制您的代码,为您在问题中提到的方法编写简单测试。这就是我得到的:

public class Transaction
{
    public string TransactionID { get; set; }
}

public class DBContext : DbContext
{
    public virtual DbSet<Transaction> Transactions { get; set; }
}

public class TransactionService
{
    public Transaction FindOrCreateTransactionByID(string id, DBContext db)
    {
        Transaction t = db.Transactions.SingleOrDefault(f => f.TransactionID == id);
        if (t == null)
        {
            t = new Transaction { TransactionID = id };
            db.Transactions.Add(t);
            db.SaveChanges();
        }
        return t;
    }
}

[TestFixture]
public class TransactionServiceTests
{
    [Test]
    public void When_transaction_not_found_new_transaction_created()
    {
        const string id = "_id_";

        var mockSet = new Mock<DbSet<Transaction>>();

        // setup data
        IQueryable<Transaction> data = new List<Transaction>().AsQueryable();
        mockSet.As<IQueryable<Transaction>>().Setup(m => m.Provider).Returns(data.Provider);
        mockSet.As<IQueryable<Transaction>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<Transaction>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<Transaction>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator);

        var mockContext = new Mock<DBContext>();
        mockContext.SetupGet(x => x.Transactions).Returns(mockSet.Object);

        var service = new TransactionService();
        service.FindOrCreateTransactionByID(id, mockContext.Object);

        mockSet.Verify(
            set => set.Add(It.Is<Transaction>(t => t.TransactionID == id)), 
            Times.Once);
        mockContext.Verify(context => context.SaveChanges(), Times.Once);
    }
}

测试是使用NUnit编写的,但您可以将其替换为您心爱的单元测试框架而不会有任何麻烦。 最困难的部分是弄清楚如何模拟事务DbSet,因此SingleOrDefault调用实际上可以在不抛出ArgumentNullException的情况下工作。正如您所看到的,这是通过模拟事务的IQueryable行为来实现的。其他一切都是一块蛋糕。

答案 1 :(得分:0)

我已经通过Typemock Isolator为您完成了一些测试示例,这样可以更轻松地模拟DBContext:

public class Transaction
{
    public string TransactionID { get; set; }
    public string TransactionName { get; set; }
}

public class DBContext : DbContext
{
    public DbSet<Transaction> Transactions { get; set; }
}

public class TransactionService
{
    public Transaction FindOrCreateTransactionByID(string id, DBContext db)
    {
        Transaction t = db.Transactions.SingleOrDefault(f => f.TransactionID == id);
        if (t == null)
        {
            t = new Transaction { TransactionID = id };
            db.Transactions.Add(t);
            db.SaveChanges();
        }
        return t;
    }
}

[TestClass]
public class UnitTest
{
    [TestMethod, Isolated]
    public void TestTransactionExist()
    {
        var service = new TransactionService();         
        string id = "id";
        string name = "name";

        var fakeDb = new DBContext();
        var fakeDbSet = Isolate.Fake.Instance<DbSet<Transaction>>();

        List<Transaction> data = new List<Transaction>()
        {
            new Transaction { TransactionID = id, TransactionName = name }
        };

        Isolate.WhenCalled(() => fakeDb.Transactions).WillReturnCollectionValuesOf(data.AsQueryable());

        Transaction res = service.FindOrCreateTransactionByID(id, fakeDb);

        Assert.AreEqual(name, res.TransactionName);
    }

    [TestMethod, Isolated]
    public void TestNewTransaction()
    {
        var service = new TransactionService();
        string id = "id";

        var fakeDb = new DBContext();
        var fakeDbSet = Isolate.Fake.Instance<DbSet<Transaction>>();

        List<Transaction> data = new List<Transaction>();

        Isolate.WhenCalled(() => fakeDb.Transactions).WillReturnCollectionValuesOf(data.AsQueryable());
        Isolate.WhenCalled(() => fakeDb.Transactions.Add(null)).DoInstead(
        context =>
        {
            data.Add(context.Parameters[0] as Transaction);
            return context.Parameters[0] as Transaction;
        });

        Transaction res = service.FindOrCreateTransactionByID(id, fakeDb);

        Assert.AreEqual(id, res.TransactionID);
    }
}

希望它有所帮助!