【问题标题】:Cannot seem to moq EF CodeFirst 4.1.Help anyone?似乎无法起订量 EF CodeFirst 4.1。帮助任何人?
【发布时间】:2011-04-24 08:04:33
【问题描述】:

我的任务是评估 codeFirst 并可能用于我们未来的所有项目。 评估基于将 codeFirst 与现有数据库一起使用。

想知道是否可以使用 codeFirst 4.1 模拟存储库。(无假货)

这个想法是将一个存储库注入到服务中,然后对存储库进行起订量。

我一直在网上找,但我只找到了一个使用假货的例子。我不想使用假货我想使用起订量。

我认为我的问题出在 DAL 的架构中。(我想使用 unitOfWork 等。我需要展示一个有效的起订量示例)

由于缺乏对 Code first 4.1 的了解,以下是我的尝试(惨败)。 我还上传了一个解决方案,以防万一有人心情好并想更改它。

http://cid-9db5ae91a2948485.office.live.com/browse.aspx/Public%20Folder?uc=1

我愿意接受建议和对我的 Dal 进行全面修改。理想情况下使用 Unity 等。但我稍后会担心。 最重要的是我需要能够模拟它。如果无法使用 MOQ,我们将使用 EF 4.1 对项目进行分箱

尝试失败

//CodeFirst.Tests Project
[TestClass]
public class StudentTests
{
    [TestMethod]
    public void Should_be_able_to_verify_that_get_all_has_been_called()
    {
        //todo redo test once i can make a simple one work
        //Arrange
        var repository = new Mock<IStudentRepository>();
        var expectedStudents = new List<Student>();
        repository.Setup(x => x.GetAll()).Returns(expectedStudents);

        //act
        var studentService = new StudentService(repository.Object);
        studentService.GetAll();

        //assert
        repository.Verify(x => x.GetAll(), Times.AtLeastOnce());
    }

}

//CodeFirst.Common Project
public class Student
{
    public int StudentId { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
}
public interface IStudentService
{
    IEnumerable<Student> GetAll();
}

//CodeFirst.Service Project
public class StudentService:IStudentService
{
    private IStudentRepository _studentRepository;

    public StudentService()
    {
    }

    public StudentService(IStudentRepository studentRepository)
    {
        _studentRepository = studentRepository;
    }


    public IEnumerable<Student> GetAll()
    {
        //TODO when mocking using moq this will actually call the db as we need a separate class.
        using (var ctx = new SchoolContext("SchoolDB"))
        {
            _studentRepository = new StudentRepository(ctx);
            var students = _studentRepository.GetAll().ToList();
            return students;
        } 
    }
}

//CodeFirst.Dal Project
public interface IRepository<T> where T : class
{
    T GetOne(Expression<Func<T, bool>> predicate);
    IEnumerable<T> GetAll();
    IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
    void Add(T entity);
    void Delete(T entity);
    T Single(Func<T, bool> predicate);
    T First(Func<T, bool> predicate);
}
public class RepositoryBase<T> : IRepository<T> where T : class
{
    private readonly IDbSet<T> _dbSet;

    public RepositoryBase(DbContext dbContext)
    {
        _dbSet = dbContext.Set<T>();
        if (_dbSet == null) throw new InvalidOperationException("Cannot create dbSet ");
    }

    protected virtual IDbSet<T> Query
    {
        get { return _dbSet; }
    }

    public T GetOne(Expression<Func<T, bool>> predicate)
    {
        return Query.Where(predicate).FirstOrDefault();
    }

    public IEnumerable<T> GetAll()
    {
        return Query.ToArray();
    }

    public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
    {
        return Query.Where(predicate).ToArray();
    }

    public void Add(T entity)
    {
        _dbSet.Add(entity);
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }


    public T Single(Func<T, bool> predicate)
    {
        return Query.Where(predicate).SingleOrDefault();
    }

    public T First(Func<T, bool> predicate)
    {
        return Query.Where(predicate).FirstOrDefault();
    }

}
 public class SchoolContext:DbContext
{
    public SchoolContext(string connectionString):base(connectionString)
    {
        Database.SetInitializer<SchoolContext>(null);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Not sure why I have to do this.Without this when using integration testing
        //as opposed to UnitTests it does not work.
        modelBuilder.Entity<Student>().ToTable("Student");       }


    public DbSet<Student> Students { get; set; }
}
public interface IStudentRepository:IRepository<Student>
{

}
public class StudentRepository : RepositoryBase<Student>, IStudentRepository
{
    public StudentRepository(DbContext dbContext)
        : base(dbContext)
    {
    }

    public IEnumerable<Student> GetStudents()
    {
        return GetAll();
    }
}

再次随意修改或任何需要帮助我整理的东西。

非常感谢您的帮助

【问题讨论】:

    标签: unit-testing mocking moq repository-pattern entity-framework-4.1


    【解决方案1】:

    当我开始使用存储库和工作单元模式时,我使用了类似于 this 的实现(它用于 ObjectContext API,但将其转换为 DbContext API 很简单)。我们将该实现与 MOQ 和 Unity 一起使用,没有任何问题。随着时间的推移,存储库和工作单元的实现以及注入的方法已经发展。后来我们发现整个这种方法存在严重的缺陷,但这已经在我引用的其他问题中讨论过here(我强烈建议您浏览这些链接)。

    令人惊讶的是,您正在评估 EFv4.1,高度重视模拟和单元测试,同时您定义了根本不可单元测试(使用模拟)的服务方法。您的服务方法的主要问题是您没有将存储库/上下文作为依赖项传递,因此您无法模拟它。测试您的服务并且不使用真实存储库的唯一方法是使用一些 very advanced approach = 用迂回替换模拟和 MOQ(例如 Moles 框架)。

    首先你必须做的是用以下代码替换你的服务代码:

    public class StudentService : IStudentService
    {
        private readonly IStudentRepository _studentRepository;
    
        public StudentService(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }
    
        public IEnumerable<Student> GetAll()
        {
             return _studentRepository.GetAll().ToList();
        }
    }
    

    顺便说一句。这绝对是无用的代码和愚蠢的分层示例,它不提供任何有用的功能。仅包装对存储库的调用仅表明根本不需要服务,并且不需要对该方法进行单元测试。这里的重点是GetAll方法的集成测试。

    无论如何,如果您想将这种方法与最小起订量结合起来,您会这样做:

    [TestClass]
    public class StudentsServiveTest
    {
        private Mock<IRespository<Student>> _repo;
    
        [TestInitialize]
        public void Init()
        {
            _repo = new Mock<IRepository<Student>>();
            _repo.Setup(r => r.GetAll()).Returns(() => new Student[] 
                { 
                    new Student { StudentId = 1, Name = "A", Surname = "B" },
                    new Student { StudentId = 2, Name = "B", Surname = "C" }
                });
        }
    
        [TestMethod]
        public void ShouldReturnAllStudents()
        {
            var service = new StudentsService(_repo.Object);
            var data = service.GetAll();
            _repo.Verify(r => r.GetAll(), Times.Once());
    
            Assert.IsNotNull(data);
            Assert.AreEqual(2, data.Count);
        }
    }
    

    【讨论】:

    • 感谢您花时间消化链接。需要服务层,因为会有很多业务验证代码等。我懒得放,它不仅仅是 DAL 的代理。在现实世界中,该服务将由第三方使用,这些服务将直接进入该服务。我只是不想用一些不会让你们清楚地帮助我的东西污染我的问题中的代码。主要目的是模拟 DAL,以便我可以验证行为而不是命中 db。在您的测试中,我可以模拟服务。(我正在注入一个 Irepository)但我不能模拟 dal。
    • 阅读了您的一些答案和经验,我注意到如果您要重新开始,您将不会使用存储库模式。好的,我需要在不使用 db 的情况下测试我的服务。Moles 似乎是一个学习曲线.MOQ 对我来说似乎是最好的选择。UnitOfwork 是否带来任何好处或只是复杂性。工作单元的想法不是抽象 EF 吗?从论坛的各种帖子来看,这似乎不是所有这些的具体答案,只是非常模糊。如果您必须测试您的服务而不是访问数据库,您将如何构建您的服务和 dal
    • 单元测试 DAL 是无稽之谈。带有 ORM 的 DAL 必须包含涉及真实数据库的集成测试,以证明映射和所有与 ORM 相关的东西都有效。
    • 考虑一下。您对 DAL 的看法是正确的。您确实需要集成测试来证明所有映射都是正确的。您找到的最佳实践是什么。AssemblyInitialize 上的数据库快照或可能是 mdf db 在您的测试项目中。
    【解决方案2】:

    我所看到的问题是您正在丢弃模拟对象并新建一个新实例

    _studentRepository = new StudentRepository(ctx);
    

    也许在接口上添加一个方法来添加上下文对象并重用在构造函数中注入的相同实例。

    using (var ctx = new SchoolContext("SchoolDB"))
        {
            _studentRepository.Context = ctx;
            var students = _studentRepository.GetAll().ToList();
            return students;
        } 
    }
    

    【讨论】:

    • 感谢您的回复。我知道我做错了什么。所以我的 IDbset 将不再是只读的。无法相信他们模拟的难度有多大
    • 您实际上并没有模拟存储库实例。您只是在模拟接口,因此 IDbSet 不会影响它。我想你可以对 StudentRepository 类本身进行单独的单元测试。
    • 这个想法是将 IStudentRepository 注入到服务中,并且永远不会访问数据库。这就是我想要实现的目标。我不打算对 studentrepository 进行单元测试,因为这会变成集成测试不? ,也许我应该。你能抽出5分钟来整理一些coden-p来说明你的意思吗?我将不胜感激,或者您知道已完成此操作的链接吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-31
    • 2017-12-14
    • 1970-01-01
    • 1970-01-01
    • 2021-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多