具有ID列的模拟数据库

时间:2016-04-04 22:56:54

标签: c# database unit-testing

我(相当)是TDD的新手,所以请保持温和!

我正在使用CodeFirst在VBS2015社区中使用EF6在C#中创建我的应用程序。

我为我的数据访问创建了一个接口,并且能够在内存中创建模拟数据库以进行单元测试。

我的每个模型都有一个唯一的标识符列,名为Id(例如,UserID)

好的,所以当我向Mock数据库添加记录时,我需要让它为记录创建一个新的ID值,因为我在后面的代码中引用它。当每个表都有唯一的ID列时,如何使用通用集执行此操作?我是否必须独立模拟每张桌子?

我的数据库界面如下所示:

Namespace Labinator2016.Lib.Headers
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    public interface ILabinatorDb : IDisposable
    {
        IQueryable<T> Query<T>() where T : class;
        void Add<T>(T entity) where T : class;
        void Delete<T1>(System.Linq.Expressions.Expression<Func<T1, bool>> target) where T1 : class;
        void Remove<T>(T entity) where T : class;
        void Update<T>(T entity) where T : class;
        void SaveChanges();
    }
}

我的模拟数据库看起来像这样:

namespace Labinator2016.Tests.TestData
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Lib.Headers;
    using Labinator2016.Lib.Models;
    class FakeDatabase : ILabinatorDb
    {
        public IQueryable<T> Query<T>() where T : class
        {
            return this.Sets[typeof(T)] as IQueryable<T>;
        }
        public void Dispose() { }
        public void AddSet<T>(IQueryable<T> objects)
        {
            this.Sets.Add(typeof(T), objects);
        }
        public void Add<T>(T entity) where T : class
        {
            this.Added.Add(entity);
            IQueryable<T> temp = this.Sets[typeof(T)] as IQueryable<T>;
            List<T> existing = temp.ToList();
            existing.Add(entity);
            Sets[typeof(T)] = existing;
        }
        void ILabinatorDb.Delete<T1>(System.Linq.Expressions.Expression<Func<T1, bool>> Target)
        {
        }
        public void Update<T>(T entity) where T : class
        {
            this.Updated.Add(entity);
        }
        public void Remove<T>(T entity) where T : class
        {
            this.Removed.Add(entity);
        }
        public void SaveChanges()
        {
            this.saved++;
        }
        public Dictionary<Type, object> Sets = new Dictionary<Type, object>();
        public List<object> Added = new List<object>();
        public List<object> Updated = new List<object>();
        public List<object> Removed = new List<object>();
        public int saved = 0;
    }
}

目前,我有一些代码可以根据名称从用户表中获取用户对象。如果它不存在,则首先创建一个。这是我的代码:

User existingUser = this.db.Query<User>().Where(u => u.UserName == newUserName).FirstOrDefault();
if (existingUser == null)
{
    User userToAdd = new User();
    userToAdd.UserName = newUserName;
    userToAdd.Password = PasswordHash.CreateHash("password");
    this.db.Add<User>(userToAdd);
    this.db.SaveChanges();
    existingUser = this.db.Query<User>().Where(u => u.EmailAddress == nu).FirstOrDefault();
}

那么,我是否在我的应用程序中完全使用了错误的方法,还是有办法模拟这个功能?

3 个答案:

答案 0 :(得分:0)

如果不确切地知道你在测试它有点难以说,但我怀疑你可以使用像Moq这样的模拟框架为你做很多繁重的工作。下面是一个简单的示例,说明如果新用户不存在于数据库中以及如何创建新用户,则如何测试新用户(使用Moq)。

    public class UserManager
{
    private readonly ILabinatorDb _liLabinatorDb;

    public UserManager(ILabinatorDb liLabinatorDb)
    {
        _liLabinatorDb = liLabinatorDb;
    }

    public void CreateNewUser(string userName)
    {
        User existingUser = _liLabinatorDb.Query<User>().FirstOrDefault(u => u.UserName == userName);

        if (existingUser == null)
        {
            User userToAdd = new User();
            userToAdd.UserName = userName;

            _liLabinatorDb.Add<User>(userToAdd);
            _liLabinatorDb.SaveChanges();
        }
    }
}

 [TestClass]
public class UnitTest1
{
    [TestMethod]
    public void GivenAUsernameWhenNoUserExistsInDatabaseThenNewUserCreated()
    {
        var mockDatabase = new Mock<ILabinatorDb>();
        mockDatabase.Setup(a => a.Query<User>()).Returns(new List<User>().AsQueryable());
        var userManager = new UserManager(mockDatabase.Object);
        userManager.CreateNewUser("UserDoesntExistInDatabase");

        mockDatabase.Verify(a => a.Add(It.IsAny<User>()),Times.Once);
        mockDatabase.Verify(a => a.SaveChanges(),Times.Once);
    }

    [TestMethod]
    public void GivenAUsernameWhenUserExistsInDatabaseThenNewUserNotCreated()
    {
        var mockDatabase = new Mock<ILabinatorDb>();
        var mockedUsers = new List<User>
        {
            new User {UserId = 2, UserName = "UserExistsInDatabase"}
        };

        mockDatabase.Setup(a => a.Query<User>()).Returns(mockedUsers.AsQueryable());
        var userManager = new UserManager(mockDatabase.Object);
        userManager.CreateNewUser("UserExistsInDatabase");

        mockDatabase.Verify(a => a.Add(It.IsAny<User>()), Times.Never);
        mockDatabase.Verify(a => a.SaveChanges(), Times.Never);
    }
}

答案 1 :(得分:0)

或者您可以在不创建具有正确框架(Typemock Isolator)的新Manager类的情况下执行此操作,我重新创建了第一个示例,以显示差异。它更短更清晰:

[TestMethod, Isolated]
public void TestAddWithEmptyDb()
{
    //Arrange
    var fakeDb = Isolate.Fake.AllInstances<FakeDatabase>(Members.CallOriginal);
    var userList = new List<User>();
    Isolate.WhenCalled(() => fakeDb.Query<User>()).WillReturnCollectionValuesOf(userList.AsQueryable());

    //Act
    UnderTest test = new UnderTest();
    test.get("newName");

    //Assert
    Isolate.Verify.WasCalledWithArguments(() => fakeDb.Add<User>(null)).Matching(a => (a[0] as User).UserName == "newName");
    Isolate.Verify.WasCalledWithAnyArguments(() => fakeDb.SaveChanges());
}

答案 2 :(得分:0)

我们的项目中也有类似的问题。

这是我们得出的结论:

  • 我们不会用逻辑模拟数据库。添加项目后,查询无法自动找到该项目。 DB-mock不是一个功能齐全的内存数据库。您必须独立模拟每个方法调用(使用模拟框架)。这大大降低了测试框架的复杂程度。
  • 我们还需要在将其添加到数据库后获取ID 。在我们的默认模拟设置中有一些反射代码,它将Id属性设置为具有一些任意值。我们的Id属性按惯例都具有相同的名称。
  • 要测试需要某种真实数据库的逻辑,比如测试复杂查询,我们使用内存中的SQLite数据库(NHibernate或多或少透明地支持它)。
  • 要测试与数据库访问和查询分开的逻辑,我会隐藏接口后面的复杂查询并模拟它。不要访问IQueriable direcly(几乎不可能模拟),而是使用GetUserByName(name)GetAllActiveUsers()等方法的接口。