我有麻烦弄清楚如何模拟我的数据库单元测试我的web api控制器。所有的资源,我发现工作的代码首先EF,但不是db第一,我的上下文是自动生成的。
简单地说,我希望能够调用我的控制器的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; }
现在我明白,我需要创建一个IContext,允许MyEntities的嘲弄,但我不知道如何组织这个。
我试着添加下面的类,但不知道如何组织它。
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);
}
- 我如何模拟我的EF数据库第一个上下文使用测试数据库?
更新11/07/2015
我看到的一些测试是:
- "搜索应该得到公司一次查询不是空的"
- "当查询为空时,搜索不应该得到公司"
- "当查询不是空的时候,搜索应该得到人们一次"
- "当查询为空时,搜索不应该得到人"
- "当查询为空时,搜索不应该添加任何公司"
- "如果从公司存储库中找到一家公司,则搜索应返回包含一家公司的结果"
- "如果从公司存储库中找到两家公司,则搜索应返回包含两家公司的结果"
- "当查询为空时,搜索不应该返回包含任何人员的结果"
- "如果从人员存储库中找到一个人,则搜索应返回包含一个人的结果"
- "如果从人员存储库中找到两个人,则搜索应返回包含两个人的结果"
首先要考虑的是,这个控制器的依赖关系是什么。从这个方法中我可以看到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);
}
您的测试看起来像(取决于哪个测试&您使用的mock框架)。请注意,这是相当粗糙的编码,将无法工作,如果粘贴!:
[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(控制反转或依赖注入容器)。你不需要这样做,但它会节省你为所有你想测试的类编写无参数构造函数的时间。
原始回答
我认为第一个问题是"你想测试什么?"当您编写测试时,应该只测试测试目标中的功能,而不测试任何依赖项。因此,您不需要连接到测试数据库来运行单元测试(除非您正在进行集成测试)。但是,您需要模拟EF DBContext。查看这篇MSDN文章,里面有很多例子