首先在 EF6 db 中模拟数据库

Mocking database in EF6 db 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 数据库第一个上下文以使用测试数据库?

2015 年 11 月 7 日更新

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

  1. "Search should get companies once where query is not empty"
  2. "Search should not get companies when query is empty"
  3. "Search should get people once when query is not empty"
  4. "Search should not get people when query is empty"
  5. "Search should not add any companies when query is empty"
  6. "Search should return a result with one company if one company found from company repository"
  7. "Search should return a result with two companies if two companies found from company repository"
  8. "Search should not return a result with any people when query is empty"
  9. "Search should return a result with one person if one person found from person repository"
  10. "Search should return a result with two people if two people found from person repository"

所以首先想到的是,这个控制器的"What are the dependencies"。从这个方法我可以看到 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(控制反转或依赖注入容器)。你不需要,但它会让你不必为所有你想测试的 类 编写无参数构造函数。

原答案

我认为第一个问题是"What are you trying to test?"。当您编写测试时,它应该只测试测试目标中的功能,而不是任何依赖项中的功能。所以可以说你不需要连接到测试数据库来 运行 你的单元测试(除非你正在进行集成测试)。但是,您将需要模拟 EF DBContext。检查这个 MSDN article 有很多例子