使用实体框架进行单元测试

本文关键字:单元测试 框架 实体 | 更新日期: 2023-09-27 18:27:03

我想使用假上下文为我的项目进行单元测试(我目前正在使用moq)。

我有以下课程:


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或任何其他lib)。然后,您将能够配置在任何情况下使用的上下文。

如果您使用ninject,更简单的解决方案将是创建接口IContext,并将其作为参数传递给服务构造函数,如:

public class EpisodiosService : IService<Episodio>
{
    private Context _context;
    public EpisodiosService(Context context)
    {
        _context = context;
    }
    ...
}

下一步是配置注入核心,在这里您可以设置将要注入的类的构造函数参数中的每个接口使用什么实现。

用于开发项目:

   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的公共属性中注入,或者创建更复杂、更通用的绑定规则。

作为更简单的解决方案,您只需创建一些方法CreateContext(),它将根据某些设置返回所需类型的上下文,并在所有方法中使用它。例如:

Context CreateContext()
{
    if (isTest)
        return TesteHelper.GerarMassaDeDados();
    return new Context();
}

但这种解决方案不如依赖注入灵活。