首先在EF6 db中模拟数据库

时间:2015-07-10 13:56:56

标签: c# entity-framework unit-testing testing ef-database-first

我无法弄清楚如何模拟我的数据库以便对我的web api控制器进行单元测试。我发现的所有资源都是首先用于代码的EF,而不是自动生成我的上下文的数据库。

简单地说,我希望能够针对我动态创建的假数据库调用我的控制器的CRUD操作,并且正在寻找最直接的方法。

我正在尝试使用http://www.nogginbox.co.uk/blog/mocking-entity-framework-data-context将它放在一起但无法管理...

我的上下文定义为:

public partial class MyEntities : DbContext
{
    public MyEntities()
        : base("name=MyEntities")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        throw new UnintentionalCodeFirstException();
    }

    public virtual DbSet<Company> Companies { get; set; }

现在我明白我需要创建一个允许模拟MyEntities的IContext,但我无法弄清楚如何组织它。

我已经尝试添加以下类,但无法弄清楚如何组织它。

public interface IContext
{
    IObjectSet<Company> Companies { get; }

    void SaveChanges();
}

public class EFContext: IContext
{
    private readonly MyEntities _data;

    public EFContext()
    {
        _data = new MyEntities();
    }

    public IObjectSet<Company> Companies
    {
        get 
        {
            return _data.CreateObjectSet<Company>();
        }
    }

    public void SaveChanges()
    {
        _data.SaveChanges();
    }
}

示例

示例控制器我想进行单元测试,如果我可以模拟数据库进行测试,这将非常容易。

public IHttpActionResult Search([FromBody]string query)
    {
        var companies = CompanyRepository.Get().Where(c => c.Name.ToLower().Contains(query.ToLower()));
        var people = PersonRepository.Get().Where(c => c.FirstName.ToLower().Contains(query.ToLower()) || c.LastName.ToLower().Contains(query.ToLower()));

        List<SearchResult> results = new List<SearchResult>();
        foreach(Company company in companies)
            results.Add(new SearchResult { ID = company.ID, Name = company.Name, Type = "Company" });
        foreach (Person person in people)
            results.Add(new SearchResult { ID = person.ID, Name = person.FirstName + " " + person.LastName, Type = "Person" });

        return Ok(results);
    }
  1. 如何模拟我的EF数据库第一个上下文以使用测试数据库?

1 个答案:

答案 0 :(得分:3)

2015年11月7日更新

所以我能看到的一些测试是:

  1. &#34;搜索应该在查询不为空的情况下获得公司&#34;
  2. &#34;当查询为空时,搜索不应该获得公司&#34;
  3. &#34;当查询不为空时,搜索应该为人们提供一次&#34;
  4. &#34;当查询为空时,搜索不应该吸引人#34;
  5. &#34;当查询为空时,搜索不应添加任何公司&#34;
  6. &#34;如果从公司存储库找到一家公司,搜索应该返回一家公司的结果&#34;
  7. &#34;如果两家公司从公司存储库中找到,搜索应该返回两家公司的结果&#34;
  8. &#34;当查询为空时,搜索不应该向任何人返回结果&#34;
  9. &#34;如果从人员存储库找到一个人,搜索应该返回一个人的结果&#34;
  10. &#34;如果有两个人在人员存储库中找到,那么搜索应该返回两个人的结果&#34;
  11. 所以首先要考虑的是,&#34;什么是依赖关系&#34;这个控制器。从这个方法我可以看到CompanyRepository和PersonRepository。这些是你想要模仿的东西。即你不在这里测试它们或它们的任何功能。您正在测试方法中的最新内容。

    您需要更改控制器,以便模拟它们,例如:

    public class MyController : ApiController 
    {
        private ICompanyRepository companyRepository;
        private IPersonRepository personRepository;
    
        public MyController ()
        {
            companyRepository = new CompanyRepository();
            personRepository = new PersonRepository();
        }
    
        public MyController (ICompanyRepository CompanyRepository, IPersonRepository PersonRepository )
        {
            companyRepository = CompanyRepository;
            personRepository = PersonRepository;
        }
    }
    

    您的代码需要引用私有存储库

    public IHttpActionResult Search([FromBody]string query)
    {
        var companies = companyRepository.Get().Where(c => c.Name.ToLower().Contains(query.ToLower()));
        var people = personRepository.Get().Where(c => c.FirstName.ToLower().Contains(query.ToLower()) || c.LastName.ToLower().Contains(query.ToLower()));
    
        List<SearchResult> results = new List<SearchResult>();
        foreach(Company company in companies)
            results.Add(new SearchResult { ID = company.ID, Name = company.Name, Type = "Company" });
        foreach (Person person in people)
            results.Add(new SearchResult { ID = person.ID, Name = person.FirstName + " " + person.LastName, Type = "Person" });
    
        return Ok(results);
    }
    

    您的测试看起来像(取决于您使用的测试和模拟框架)。请注意,这是非常粗略的编码,如果粘贴则无法工作!:

    [TestClass]
    public class MyControllerTests
    {
        Mock<ICompanyRepository>() mockCompanyRepository = new Mock<ICompanyRepository>()
        Mock<IPersonRepository>() mockPersonRepository = new Mock<IPersonRepository>()
        MyController sut;
    
        Public MyControllerTests ()
        {
            // Create your "System under test" which is your MyController. Pass in your mock repositories to aid your testing.
            sut = new MyController(mockCompanyRepository.Object, mockPersonRepository.Object)
        }
    
        [TestMethod]
        public void SearchShouldGetCompaniesOnceWhereQueryIsNotEmpty
        {
            //Arrange
            var expectedCompanies = new List<Company>{new Company{"Company1"}, new Company{"Company2"}};
            //Setup mock that will return People list when called:
            mockCompanyRepository.Setup(x => x.Get()).Returns(expectedCompanies);
            mockPersonRepository.Setup(x => x.Get()).Returns(new List<Person>())
    
        //Act
        var result = sut.Search("Conway");
    
        //Assert - check the company repository was called once and the result was as expected
        mockCompanyRepository.Verify(x => x.Get(), Times.Once());
    
    OkNegotiatedContentResult<string> conNegResult = Assert.IsType<OkNegotiatedContentResult<string>>(actionResult);
    Assert.Equal("data: [{Name : "Company1"}, {Name : "Company2"}]", conNegResult.Content);
        }
    }
    

    您还没有在问题中包含您的存储库的代码,但如果您使用上述样式,则应该能够遵循相同的模式。如果您的存储库Get方法没有逻辑并且只是一个传递,那么我认为您不需要针对它编写测试。如果你正在做诸如订购,过滤等等时髦的事情,那么我会说你做的。但是,您然后以与上述非常类似的方式测试存储库!

    快速记录。 MyController构造函数有2个方法。一个是无参数的,另一个是你的存储库。我这样做了,因为我不知道你是否使用了IoC(控制反转或依赖注入容器)。您不需要,但它将节省您必须为您要测试的所有类编写无参数构造函数。

    原始答案

    我认为第一个问题是&#34;你想测试什么?&#34;。在编写测试时,它应该只测试测试目标中的功能而不是任何依赖项。所以你可能不需要连接到测试数据库来运行单元测试(除非你正在进行集成测试)。但是,您需要模拟EF DBContext。查看具有大量示例的MSDN article