2016-09-14 79 views
6

我第一次尝试.NET核心,并看到如何在单元测试中使用Moq。开箱即用,在创建控制器,其中ApplicationDbContext是参数的构造函数是这样的:我如何在.NET核心Moq ApplicationDbContext

public class MoviesController : Controller 
{ 
    private readonly ApplicationDbContext _context; 

    public MoviesController(ApplicationDbContext context) 
    { 
     _context = context;  
    } 

这里的单元测试,我开始测试控制器时:

[TestClass] 
public class MvcMoviesControllerTests 
{ 
    [TestMethod] 
    public async Task MoviesControllerIndex() 
    { 
     var mockContext = new Mock<ApplicationDbContext>();    
     var controller = new MoviesController(mockContext.Object); 

     // Act 
     var result = await controller.Index(); 

     // Assert 
     Assert.IsInstanceOfType(result, typeof(ViewResult)); 
    } 

但随后我意识到ApplicationDbContext是一个具体的类,它没有无参数的构造函数,所以测试不起作用。它给了我错误:无法找到无参数的构造函数。

也许这可能是一个更多针对Moq的问题,而不是它与.NET Core相关,但我也是Moq的新手,所以我不知道如何继续。下面是当我创建项目ApplicationDbContext代码是如何产生的:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser> 
{ 
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 
     : base(options) 
    { 
    } 

    protected override void OnModelCreating(ModelBuilder builder) 
    { 
     base.OnModelCreating(builder); 
     // Customize the ASP.NET Identity model and override the defaults if needed. 
     // For example, you can rename the ASP.NET Identity table names and more. 
     // Add your customizations after calling base.OnModelCreating(builder); 
    } 

    public DbSet<Movie> Movie { get; set; } 
} 

需要什么改变让我的单元测试会成功吗?

UPDATE:

我从https://msdn.microsoft.com/en-us/magazine/mt703433.aspx发现,您可以配置EF核心使用内存数据库进行单元测试。所以我改变我的单元测试看起来像这样:

[TestMethod] 
    public async Task MoviesControllerIndex() 
    {   
     var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>(); 
     optionsBuilder.UseInMemoryDatabase(); 
     var _dbContext = new ApplicationDbContext(optionsBuilder.Options); 

     var controller = new MoviesController(_dbContext); 

     // Act 
     var result = await controller.Index(); 

     // Assert 
     Assert.IsInstanceOfType(result, typeof(ViewResult)); 
    } 

这个测试现在成功了。但是,这是做这件事的正确方法吗?显然,我完全消除了用Moq嘲笑ApplicationDbContext!还是有另一种解决方案来解决这个问题使用Moq。

+0

即使使用内存中的DbContext,您也基本上正在进行集成测试,这仍然是必需的。然而,从设计的角度来看,你应该让你的课程取决于抽象而不是结核。创建一个接口,公开您需要的功能并让您的具体上下文继承于此。 – Nkosi

+1

我同意@Nkosi这是现在有效的集成测试。当ApplicationDbContext实现'IdentityDbContext '时,你可能可以将接口注入到你的控制器中,然后模拟它吗? – Corporalis

回答

4

您不应该尝试直接模拟数据库上下文。而是实施Repository Pattern并改为替代模型库。

这里有一个great guide关于如何正确实现模式。

4

嘲笑DbContext不起作用,因为有太多的提供者需要使它工作。更简单的解决方案是使用Microsoft为此确实目的而实施的InMemory解决方案。请不要创建仅用于测试的回购(它仍不会测试EF代码)。

下面是如何与InMemory数据库在.NET核心

https://docs.efproject.net/en/latest/miscellaneous/testing.html

1

如果有人有兴趣,我@Corporalis的理念的一部分,并实现了一个接口暴露ApplicationDbContext测试链路测试项目。

public interface IApplicationDbContext 
{ 
    DbSet<Movie> Movies { get; set; } 

    int SaveChanges(); 
} 

而且在ApplicationDbContext

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IApplicationDbContext 
{ 
    public virtual DbSet<Movie> Movies { get; set; } 

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 
     : base(options) 
    { 
    } 

    protected override void OnModelCreating(ModelBuilder builder) 
    { 
     base.OnModelCreating(builder); 
    } 
} 

鸵鸟政策forguet注册在启动时的界面。CS,ConfigureServices

services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>()); 

所以,现在在控制器或服务,你拥有的每使用ApplicationDbContext方法,这样做是为了在接口的引用,而不是

public class filterMovies 
{ 
    private readonly IApplicationDbContext _context 

    public filterMovies(IApplicationDbContext context) 
    { 
    _context = context; 
    } 
} 

现在我们随意嘲笑使用接口,而不是实现它的自我

var mockContext = new Mock<IApplicationDbContext>(); 
mockContext.Setup(mc => mc.Movies).Returns(mockDbSet.Object); 

的ApplicationDbContext我希望它能帮助;)