使用 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();
}

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