首先在 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);
}
- 如何模拟我的 EF 数据库第一个上下文以使用测试数据库?
2015 年 11 月 7 日更新
所以我能看到的一些测试是:
- "Search should get companies once where query is not empty"
- "Search should not get companies when query is empty"
- "Search should get people once when query is not empty"
- "Search should not get people when query is empty"
- "Search should not add any companies when query is empty"
- "Search should return a result with one company if one company found from company repository"
- "Search should return a result with two companies if two companies found from company repository"
- "Search should not return a result with any people when query is empty"
- "Search should return a result with one person if one person found from person repository"
- "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 有很多例子
我无法弄清楚如何模拟我的数据库以对我的 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);
}
- 如何模拟我的 EF 数据库第一个上下文以使用测试数据库?
2015 年 11 月 7 日更新
所以我能看到的一些测试是:
- "Search should get companies once where query is not empty"
- "Search should not get companies when query is empty"
- "Search should get people once when query is not empty"
- "Search should not get people when query is empty"
- "Search should not add any companies when query is empty"
- "Search should return a result with one company if one company found from company repository"
- "Search should return a result with two companies if two companies found from company repository"
- "Search should not return a result with any people when query is empty"
- "Search should return a result with one person if one person found from person repository"
- "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 有很多例子