.net 要测试的实体框架4.1的虚拟DbContext
我使用本教程模拟我的DbContext并测试: 但我必须将FakeMainModuleContext实现更改为在控制器中使用:.net 要测试的实体框架4.1的虚拟DbContext,.net,asp.net-mvc,unit-testing,entity-framework,tdd,.net,Asp.net Mvc,Unit Testing,Entity Framework,Tdd,我使用本教程模拟我的DbContext并测试: 但我必须将FakeMainModuleContext实现更改为在控制器中使用: public class FakeQuestiona2011Context : IQuestiona2011Context { private IDbSet<Credencial> _credencial; private IDbSet<Perfil> _perfil; private IDbSet<Apurador
public class FakeQuestiona2011Context : IQuestiona2011Context
{
private IDbSet<Credencial> _credencial;
private IDbSet<Perfil> _perfil;
private IDbSet<Apurador> _apurador;
private IDbSet<Entrevistado> _entrevistado;
private IDbSet<Setor> _setor;
private IDbSet<Secretaria> _secretaria;
private IDbSet<Pesquisa> _pesquisa;
private IDbSet<Pergunta> _pergunta;
private IDbSet<Resposta> _resposta;
public IDbSet<Credencial> Credencial { get { return _credencial ?? (_credencial = new FakeDbSet<Credencial>()); } set { } }
public IDbSet<Perfil> Perfil { get { return _perfil ?? (_perfil = new FakeDbSet<Perfil>()); } set { } }
public IDbSet<Apurador> Apurador { get { return _apurador ?? (_apurador = new FakeDbSet<Apurador>()); } set { } }
public IDbSet<Entrevistado> Entrevistado { get { return _entrevistado ?? (_entrevistado = new FakeDbSet<Entrevistado>()); } set { } }
public IDbSet<Setor> Setor { get { return _setor ?? (_setor = new FakeDbSet<Setor>()); } set { } }
public IDbSet<Secretaria> Secretaria { get { return _secretaria ?? (_secretaria = new FakeDbSet<Secretaria>()); } set { } }
public IDbSet<Pesquisa> Pesquisa { get { return _pesquisa ?? (_pesquisa = new FakeDbSet<Pesquisa>()); } set { } }
public IDbSet<Pergunta> Pergunta { get { return _pergunta ?? (_pergunta = new FakeDbSet<Pergunta>()); } set { } }
public IDbSet<Resposta> Resposta { get { return _resposta ?? (_resposta = new FakeDbSet<Resposta>()); } set { } }
public void SaveChanges()
{
// do nothing (probably set a variable as saved for testing)
}
}
公共类伪造问题2011上下文:iquestiona2011上下文
{
私人IDbSet _credencial;
专用IDbSet _perfil;
私有IDbSet_apurador;
私营企业;
专用IDbSet setor;
私人秘书处;
私有IDbSet_pesquisa;
私有IDbSet_pergunta;
私有IDbSet _resposta;
public IDbSet Credencial{get{return{u Credencial???(\u Credencial=new FakeDbSet());}set{}
公共IDbSet Perfil{get{return{u Perfil???(\u Perfil=new FakeDbSet());}set{}
公共IDbSet Apurador{get{return}u Apurador???(_Apurador=new FakeDbSet());}set{}
公共IDbSet Entrevistado{get{return}Entrevistado???(\u Entrevistado=new FakeDbSet());}set{}
公共IDbSet集合或{get{return}Setor???(\U Setor=new FakeDbSet());}set{}
公共IDbSet SEBRIA{get{return{U SEBRIA???(\U SERRIA=new FakeDbSet());}set{}
公共IDbSet PESQISA{get{return{U PESQISA???(\U PESQISA=new FakeDbSet());}set{}
公共IDbSet Pergunta{get{return{U Pergunta???(\U Pergunta=new FakeDbSet());}set{}
公共IDbSet Resposta{get{return}u Resposta???(_Resposta=new FakeDbSet());}set{}
公共void SaveChanges()
{
//不执行任何操作(可能将变量设置为已保存以供测试)
}
}
我的测试是这样的:
[TestMethod]
public void IndexTest()
{
IQuestiona2011Context fakeContext = new FakeQuestiona2011Context();
var mockAuthenticationService = new Mock<IAuthenticationService>();
var apuradores = new List<Apurador>
{
new Apurador() { Matricula = "1234", Nome = "Acaz Souza Pereira", Email = "acaz@telecom.inf.br", Ramal = "1234" },
new Apurador() { Matricula = "4321", Nome = "Samla Souza Pereira", Email = "samla@telecom.inf.br", Ramal = "4321" },
new Apurador() { Matricula = "4213", Nome = "Valderli Souza Pereira", Email = "valderli@telecom.inf.br", Ramal = "4213" }
};
apuradores.ForEach(apurador => fakeContext.Apurador.Add(apurador));
ApuradorController apuradorController = new ApuradorController(fakeContext, mockAuthenticationService.Object);
ActionResult actionResult = apuradorController.Index();
Assert.IsNotNull(actionResult);
Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
ViewResult viewResult = (ViewResult)actionResult;
Assert.IsInstanceOfType(viewResult.ViewData.Model, typeof(IndexViewModel));
IndexViewModel indexViewModel = (IndexViewModel)viewResult.ViewData.Model;
Assert.AreEqual(3, indexViewModel.Apuradores.Count);
}
[TestMethod]
公共无效索引()
{
IQuestiona2011Context fakeContext=新的FakeQuestiona2011Context();
var mockAuthenticationService=new Mock();
var apuradores=新列表
{
new Apurador(){Matricula=“1234”,Nome=“Acaz Souza Pereira”,Email=”acaz@telecom.inf.br,Ramal=“1234”},
新Apurador(){Matricula=“4321”,Nome=“Samla Souza Pereira”,电子邮件=”samla@telecom.inf.br,Ramal=“4321”},
新的Apurador(){Matricula=“4213”,Nome=“Valderli Souza Pereira”,电子邮件=”valderli@telecom.inf.br,Ramal=“4213”}
};
ForEach(apurador=>fakeContext.apurador.Add(apurador));
ApuradorController ApuradorController=新的ApuradorController(fakeContext,mockAuthenticationService.Object);
ActionResult ActionResult=apuradorController.Index();
Assert.IsNotNull(actionResult);
IsInstanceOfType(actionResult,typeof(ViewResult));
ViewResult ViewResult=(ViewResult)actionResult;
Assert.IsInstanceOfType(viewResult.ViewData.Model,typeof(IndexViewModel));
IndexViewModel IndexViewModel=(IndexViewModel)viewResult.ViewData.Model;
arenequal(3,indexViewModel.Apuradores.Count);
}
我做得对吗?不幸的是,你做得不对,因为那篇文章是错的。它假装
FakeContext
将使您的代码单元可测试,但事实并非如此。一旦您将IDbSet
或IQueryable
暴露给控制器,并使用内存中的集合伪造该集合,您就永远无法确定单元测试是否真的测试了代码。在控制器中编写LINQ查询非常容易,它将通过单元测试(因为FakeContext
使用LINQ访问对象),但在运行时失败(因为实际上下文使用LINQ访问实体)。这使得单元测试的全部目的毫无用处
我的意见是:如果您想将集合公开给控制器,就不要费心伪造上下文。而是使用与真实数据库的集成测试进行测试。这是验证控制器中定义的LINQ查询是否达到预期效果的唯一方法
当然,如果您只想在集合上调用ToList
或FirstOrDefault
,您的FakeContext
将很好地为您服务,但一旦您做了更复杂的事情,您很快就会发现一个陷阱(只需输入字符串“无法转换为存储表达式”进入谷歌-所有这些问题只会在您运行Linq to实体时出现,但它们会通过您使用Linq to对象的测试)
这是一个非常常见的问题,因此您可以查看其他一些示例:
<connectionStrings>
<add name="MyDbContext"
connectionString="Data Source=|DataDirectory|MyDb.sdf"
providerName="System.Data.SqlServerCe.4.0"
/>
</connectionStrings>
.
Database.SetInitializer(新的DropCreateDatabaseAlways());
然后,在运行每个测试之前强制EF初始化
public void Setup() {
Database.SetInitializer<MyDbContext>(new DropCreateDatabaseAlways<MyDbContext>());
context = new MyDbContext();
context.Database.Initialize(force: true);
}
公共作废设置(){
SetInitializer(新的DropCreateDatabaseAlways());
context=新的MyDbContext();
context.Database.Initialize(强制:true);
}
如果您使用的是xunit,请在构造函数中调用Setup方法。如果您使用的是MSTest,请将TestInitializeAttribute放在该方法上。如果是
“不幸的是,你做得不对,因为那篇文章是错误的。它假装FakeContext会使你的代码单元可测试,但它不会”
我是你提到的博客文章的创建者。我在这里看到的问题是对N层单元测试基本原理的误解。我的帖子并不打算直接用于测试控制器逻辑
单元测试应该完全按照名称的含义进行,并测试“一个单元”。如果我正在测试一个控制器(正如您在上面所做的),我会忘记所有关于数据访问的事情。我应该在脑海中删除对数据库上下文的所有调用,并将它们替换为一个黑盒方法调用,就好像这些操作对我来说是未知的一样。我感兴趣的是测试这些操作的代码
示例:
在我的MVC应用程序中,我们使用代表
public void Setup() {
Database.SetInitializer<MyDbContext>(new DropCreateDatabaseAlways<MyDbContext>());
context = new MyDbContext();
context.Database.Initialize(force: true);
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
namespace MockFactory
{
public class TestDbSet<TEntity> : DbSet<TEntity>, IQueryable, IEnumerable<TEntity>, IDbAsyncEnumerable<TEntity>
where TEntity : class
{
public readonly ObservableCollection<TEntity> _data;
private readonly IQueryable _query;
private readonly Dictionary<Type, object> entities;
public TestDbSet()
{
_data = new ObservableCollection<TEntity>();
_query = _data.AsQueryable();
entities = new Dictionary<Type, object>();
}
public override ObservableCollection<TEntity> Local
{
get { return _data; }
}
IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.GetAsyncEnumerator()
{
return new TestDbAsyncEnumerator<TEntity>(_data.GetEnumerator());
}
IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
return _data.GetEnumerator();
}
Type IQueryable.ElementType
{
get { return _query.ElementType; }
}
Expression IQueryable.Expression
{
get { return _query.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return new TestDbAsyncQueryProvider<TEntity>(_query.Provider); }
}
IEnumerator IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
public void AddNavigationProperty<T>(DbSet<T> dbSet) where T : class
{
entities.Add(typeof (T), dbSet);
}
public void RefreshNavigationProperty(TEntity item)
{
foreach (var entity in entities)
{
var property = item.GetType().GetProperty(entity.Key.Name);
var type =
(int)item.GetType().GetProperty(entity.Key.Name.Replace(typeof(TEntity).Name, "")).GetValue(item);
var dbSets = (IEnumerable<object>)entity.Value.GetType().GetField("_data").GetValue(entity.Value);
var dbSet = dbSets.Single(x => (int)x.GetType().GetProperty("Id").GetValue(x) == type);
property.SetValue(item, dbSet);
}
}
public override TEntity Add(TEntity item)
{
RefreshNavigationProperty(item);
_data.Add(item);
return item;
}
public override TEntity Remove(TEntity item)
{
_data.Remove(item);
return item;
}
public override TEntity Attach(TEntity item)
{
_data.Add(item);
return item;
}
public override TEntity Create()
{
return Activator.CreateInstance<TEntity>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
}
}
public TestContext()
{
TypeUsers = new TestDbSet<TypeUser>();
StatusUsers = new TestDbSet<StatusUser>();
TypeUsers.Add(new TypeUser {Description = "FI", Id = 1});
TypeUsers.Add(new TypeUser {Description = "HR", Id = 2});
StatusUsers.Add(new StatusUser { Description = "Created", Id = 1 });
StatusUsers.Add(new StatusUser { Description = "Deleted", Id = 2 });
StatusUsers.Add(new StatusUser { Description = "PendingHR", Id = 3 });
Users = new TestDbSet<User>();
((TestDbSet<User>) Users).AddNavigationProperty(StatusUsers);
((TestDbSet<User>)Users).AddNavigationProperty(TypeUsers);
}
public override DbSet<TypeUser> TypeUsers { get; set; }
public override DbSet<StatusUser> StatusUsers { get; set; }
public override DbSet<User> Users { get; set; }
public int SaveChangesCount { get; private set; }
public override int SaveChanges(string modifierId)
{
SaveChangesCount++;
return 1;
}
}
ContextFactory.Entity.Users.Each(((TestDbSet<User>) ContextFactory.Entity.Users).RefreshNavigationProperty);