[英]Mocking database in EF6 db first
I'm having trouble figuring out how to mock my database for unit testing my web api controllers. 我无法弄清楚如何模拟我的数据库以进行单元测试我的web api控制器。 All the resources I've found work for code first EF but not db first where my context is auto-generated. 我发现的所有资源都是首先用于代码的EF,而不是自动生成我的上下文的db。
Put simply I want to be able to call my controller's CRUD operations against a fake database I create on the fly and am looking for the most straight forward method of doing this. 简单地说,我希望能够针对我动态创建的假数据库调用我的控制器的CRUD操作,并且正在寻找最直接的方法。
I'm trying to use http://www.nogginbox.co.uk/blog/mocking-entity-framework-data-context to put it together but cannot manage... 我正在尝试使用http://www.nogginbox.co.uk/blog/mocking-entity-framework-data-context将它放在一起但无法管理...
My context is defined as: 我的上下文定义为:
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; }
Now I understand that I need to create an IContext which allows the mocking of MyEntities, but I cannot figure out how to organize this. 现在我明白我需要创建一个允许模拟MyEntities的IContext,但我无法弄清楚如何组织它。
I've tried adding the below classes but can't figure out how to organize it past this. 我已经尝试添加以下类,但无法弄清楚如何组织它。
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();
}
}
Example 例
Example controller I'd like to unit test, which would be really easy to do if I could mock a database to test against. 示例控制器我想进行单元测试,如果我可以模拟数据库进行测试,这将非常容易。
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);
}
Updated 11/07/2015 2015年7月11日更新
So some tests that I can see are: 所以我能看到的一些测试是:
So the first thing to think about is, "What are the dependencies" of this controller. 所以首先要考虑的是,这个控制器的“依赖性是什么”。 From this method I can see the CompanyRepository and the PersonRepository. 从这个方法我可以看到CompanyRepository和PersonRepository。 These are the things you want to mock. 这些是你想要模仿的东西。 ie you aren't testing them here or any of their functionality. 即你没有在这里测试它们或它们的任何功能。 You are testing only whats in your method. 您正在测试方法中的最新情况。
You will want to change your Controller so you can mock them eg: 您将需要更改您的控制器,以便您可以模拟它们,例如:
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;
}
}
Your code then needs to reference the private repositories 然后,您的代码需要引用私有存储库
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);
}
Your tests then look something like (depending on which test & mocking framework you use). 然后你的测试看起来像(取决于你使用的测试和模拟框架)。 Please note this is quite rough coding and won't work if pasted!: 请注意这是非常粗略的编码,如果粘贴将无法工作!:
[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);
}
}
You haven't included the code for your repositories in the question but if you use the above style you should be able to follow the same pattern. 您没有在问题中包含存储库的代码,但如果使用上述样式,则应该能够遵循相同的模式。 If your repository Get method has no logic in and is simply a pass through, then I would argue you don't need to write a test against it. 如果您的存储库Get方法没有逻辑并且只是一个传递,那么我认为您不需要针对它编写测试。 If you are doing funky things like ordering, filtering etc etc then I would say you do. 如果你正在做诸如订购,过滤等等时髦的事情,那么我会说你做的。 But you then test the repository by itself and in a very similar way to the above! 但是,您然后以与上述非常相似的方式自行测试存储库!
Quick note. 快速说明。 The MyController constructor has 2 methods. MyController构造函数有2个方法。 One is parameterless and the other takes your repositories. 一个是无参数的,另一个是你的存储库。 I have done this as I do not know if you are using an IoC (Inversion of Control or Dependency Injection container). 我这样做了,因为我不知道你是否使用了IoC(控制反转或依赖注入容器)。 You don't need to but it will save you having to write parameterless constructors for all the classes you want to test. 您不需要,但它将节省您必须为您要测试的所有类编写无参数构造函数。
Original Answer 原始答案
I think the first question is "What are you trying to test?". 我认为第一个问题是“你想测试什么?”。 When you write a test it should be testing just the functionality in the test target and not in any dependencies. 在编写测试时,它应该只测试测试目标中的功能而不是任何依赖项。 So arguably you shouldn't need to connect to a test database to run your unit test (unless you are doing an integration test). 因此可以说,您不需要连接到测试数据库来运行单元测试(除非您正在进行集成测试)。 You will need to mock out the EF DBContext however. 但是,您需要模拟EF DBContext。 Check this MSDN article which has plenty of examples 查看这篇包含大量示例的MSDN文章
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.