使用 entity framework 进行单元测试
Unit tests with entity framework
我想使用假上下文为我的项目进行单元测试(我目前正在为此使用最小起订量)。
我有以下 类:
EpisodiosService.cs
public class EpisodiosService : IService<Episodio>
{
private Context _context;
public EpisodiosService(Context context = null)
{
if (context == null)
{
context = new Context();
}
_context = context;
}
...
}
TesteHelper.cs
public class TesteHelper
{
public static List<Episodio> lstEpisodios { get; set; }
public static Mock<Context> mockContext { get; set; }
public static Mock<Context> GerarMassaDeDados()
{
...
var mockSetEpisodio = new Mock<DbSet<Episodio>>();
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.Provider).Returns(lstEpisodios.AsQueryable().Provider);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.Expression).Returns(lstEpisodios.AsQueryable().Expression);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.ElementType).Returns(lstEpisodios.AsQueryable().ElementType);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.GetEnumerator()).Returns(lstEpisodios.AsQueryable().GetEnumerator());
mockContext = new Mock<Context>();
mockContext.Setup(x => x.Episodio).Returns(mockSetEpisodio.Object);
EpisodiosService episodiosService = new EpisodiosService(mockContext.Object);
return mockContext;
}
Episodio.cs
public class Episodio : ModelBase
{
...
public Episodio()
{
nIdEstadoEpisodio = Enums.EstadoEpisodio.Ignorado;
lstIntEpisodios = new List<int>();
lstIntEpisodiosAbsolutos = new List<int>();
}
public bool IdentificarEpisodio()
{
...
EpisodiosService episodiosService = new EpisodiosService();
List<Episodio> lstEpisodios = episodiosService.GetLista(oSerie);
...
}
因此,如果在测试方法中我放入一些代码,如 var service = new EpisodiosService(TesteHelper.GerarMassaDeDados())
并使用此服务,我将按预期获得模拟内容,但某些实体中有一些方法使用该服务,并且我不能像 Episodio.IdentificarEpisodio()
那样传递模拟上下文,如果我创建 Episodio
的实例并调用 IdentificarEpisodio()
,它将不会使用模拟上下文,因为它没有被传递。
有没有办法让服务使用模拟上下文而不更改其签名(例如 IdentificarEpisodio(Context context)
)?
我不想更改它的签名,因为有很多方法都有同样的问题,我必须更改,而且我认为全部更改不会很好.. .
提前致谢。
我认为解决该问题的最佳方法是使用依赖注入(您可以为此使用 ninject 或任何其他库)。然后您将能够配置在任何情况下要使用的上下文。
如果您使用 ninject 更简单的解决方案是创建接口 IContext 并将其作为参数传递给服务构造函数,例如:
public class EpisodiosService : IService<Episodio>
{
private Context _context;
public EpisodiosService(Context context)
{
_context = context;
}
...
}
下一步是配置注入核心,您可以在class的构造函数参数中为每个接口设置要使用的实现,将被注入。
对于开发项目:
var kernel = new StandardKernel();
kernel.Bind<IContext>().To<Context>();
对于单元测试:
var kernel = new StandardKernel();
kernel.Bind<IContext>().ToMethod(e => TesteHelper.GerarMassaDeDados());
然后你就可以使用这个核心来获得你的服务了:
var service = kernel.Get<EpisodiosService>();
通过这种方式,您将获得每个案例所需的上下文。
请注意,配置注入的选项更多,例如,您可以注入标记为 InjectAttribute 的 public 属性或创建更复杂和通用的绑定规则。
作为更简单的解决方案,您可以创建一些方法 CreateContext()
,根据某些设置 return 需要上下文类型,并在您的所有方法中使用它。例如:
Context CreateContext()
{
if (isTest)
return TesteHelper.GerarMassaDeDados();
return new Context();
}
但是这个解决方案不如依赖注入灵活。
我想使用假上下文为我的项目进行单元测试(我目前正在为此使用最小起订量)。
我有以下 类:
EpisodiosService.cs
public class EpisodiosService : IService<Episodio>
{
private Context _context;
public EpisodiosService(Context context = null)
{
if (context == null)
{
context = new Context();
}
_context = context;
}
...
}
TesteHelper.cs
public class TesteHelper
{
public static List<Episodio> lstEpisodios { get; set; }
public static Mock<Context> mockContext { get; set; }
public static Mock<Context> GerarMassaDeDados()
{
...
var mockSetEpisodio = new Mock<DbSet<Episodio>>();
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.Provider).Returns(lstEpisodios.AsQueryable().Provider);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.Expression).Returns(lstEpisodios.AsQueryable().Expression);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.ElementType).Returns(lstEpisodios.AsQueryable().ElementType);
mockSetEpisodio.As<IQueryable<Episodio>>().Setup(m => m.GetEnumerator()).Returns(lstEpisodios.AsQueryable().GetEnumerator());
mockContext = new Mock<Context>();
mockContext.Setup(x => x.Episodio).Returns(mockSetEpisodio.Object);
EpisodiosService episodiosService = new EpisodiosService(mockContext.Object);
return mockContext;
}
Episodio.cs
public class Episodio : ModelBase
{
...
public Episodio()
{
nIdEstadoEpisodio = Enums.EstadoEpisodio.Ignorado;
lstIntEpisodios = new List<int>();
lstIntEpisodiosAbsolutos = new List<int>();
}
public bool IdentificarEpisodio()
{
...
EpisodiosService episodiosService = new EpisodiosService();
List<Episodio> lstEpisodios = episodiosService.GetLista(oSerie);
...
}
因此,如果在测试方法中我放入一些代码,如 var service = new EpisodiosService(TesteHelper.GerarMassaDeDados())
并使用此服务,我将按预期获得模拟内容,但某些实体中有一些方法使用该服务,并且我不能像 Episodio.IdentificarEpisodio()
那样传递模拟上下文,如果我创建 Episodio
的实例并调用 IdentificarEpisodio()
,它将不会使用模拟上下文,因为它没有被传递。
有没有办法让服务使用模拟上下文而不更改其签名(例如 IdentificarEpisodio(Context context)
)?
我不想更改它的签名,因为有很多方法都有同样的问题,我必须更改,而且我认为全部更改不会很好.. .
提前致谢。
我认为解决该问题的最佳方法是使用依赖注入(您可以为此使用 ninject 或任何其他库)。然后您将能够配置在任何情况下要使用的上下文。
如果您使用 ninject 更简单的解决方案是创建接口 IContext 并将其作为参数传递给服务构造函数,例如:
public class EpisodiosService : IService<Episodio>
{
private Context _context;
public EpisodiosService(Context context)
{
_context = context;
}
...
}
下一步是配置注入核心,您可以在class的构造函数参数中为每个接口设置要使用的实现,将被注入。
对于开发项目:
var kernel = new StandardKernel();
kernel.Bind<IContext>().To<Context>();
对于单元测试:
var kernel = new StandardKernel();
kernel.Bind<IContext>().ToMethod(e => TesteHelper.GerarMassaDeDados());
然后你就可以使用这个核心来获得你的服务了:
var service = kernel.Get<EpisodiosService>();
通过这种方式,您将获得每个案例所需的上下文。
请注意,配置注入的选项更多,例如,您可以注入标记为 InjectAttribute 的 public 属性或创建更复杂和通用的绑定规则。
作为更简单的解决方案,您可以创建一些方法 CreateContext()
,根据某些设置 return 需要上下文类型,并在您的所有方法中使用它。例如:
Context CreateContext()
{
if (isTest)
return TesteHelper.GerarMassaDeDados();
return new Context();
}
但是这个解决方案不如依赖注入灵活。