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

时间:2015-12-17 01:20:02

标签: c# entity-framework unit-testing moq

我想使用假上下文为我的项目进行单元测试(我目前正在使用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)为例)?

我不想改变它的签名,因为有很多方法都有同样的问题,而且我不得不改变,而且我认为改变这一切并不好...... / p>

提前致谢。

1 个答案:

答案 0 :(得分:-1)

在我看来,解决该问题的最佳方法是使用依赖注入(您可以使用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();
}

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