当一个博客包含许多 Post 时,如何模拟博客和 Post

How to mock up Blog and Post when one Blog contains many Posts

假设我们有

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }

    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

我想在单元测试中为 BlogPost 播种数据。但是博客包含 Posts 并且 Post 包含博客。

[编辑]

我想将 DbContext 传递给控制器​​。因此我必须模拟 DbContextDbSet。对于 DbSet,我需要播种一些虚拟数据。

例如:
在 asp.net mvc 控制器中

public IActionResult GetBlog(int id)
{
    Blog blog = _context.Blog.FirstOrDefault(x => x.BlogId == id);
    return View(Blog);
}

测试中

[Fact]
public void Get_blog_1_returns_google()
{
    // Act
    var result = _controller.GetBlog(1) as ViewResult;

    // Assert
    Assert.IsType(typeof(Blog), result.ViewData.Model);
    Blog model = (Blog)result.ViewData.Model;
    Assert.Equal("google", model.Url);
}

但我必须先为测试播种数据。

public class BlogControllerTest
{
    private BloggingContext _context;
    private BlogController _controller;

    public BlogControllerTest()
    {
        // Seed data
        _context.Blog.Add(new Blog()
        {
            BlogId = 1,
            Url = "google",
            // how about Post
        });
        //..other code...
    }
    //...other code removed for brevity
}

如何模拟它们?

首先你应该创建一些抽象来让你的DbContext更容易mock/test

public interface IDbContext : IDisposable {
    int SaveChanges();
}

public interface IBloggingContext : IDbContext {
    DbSet<Blog> Blogs { get; }
    DbSet<Post> Posts { get; }
}

有了这些抽象,您就可以实现具体的 BloggingContext

public class BloggingContext : DbContext, IBloggingContext {
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options) { }

    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

假设您的 BlogController 正在使用构造函数注入...

public class BlogController : Controller {
    private IBloggingContext _context;

    public BlogController(IBloggingContext bloggingContext) {
        this._context = bloggingContext;
    }

    public IActionResult GetBlog(int id) {
        Blog blog = _context.Blogs.FirstOrDefault(x => x.BlogId == id);
        return View(blog);
    }
}

您现在可以使用像 Moq 这样的模拟框架来模拟 IBloggingContext 到 return fake/mocked Blog 的实例和 Post.

public class BlogControllerTest {
    private IBloggingContext _context;
    private BlogController _controller;

    public BlogControllerTest() {

        //creating seed objects
        var post = new Post() {
            PostId = 1,
            Title = "Some title",
            Content = "post contemt"
        };

        var blog = new Blog() {
            BlogId = 1,
            Url = "google",
            Posts = new List<Post>() { post }
        };

        //associating blog with post
        post.Blog = blog;
        post.BlogId = blog.BlogId;

        var blogsMock = new List<Blog>() { blog }.AsDbSetMock();
        var postsMock = new List<Post>() { post }.AsDbSetMock();

        var contextMock = new Mock<IBloggingContext>();
        contextMock.Setup(m => m.Blogs).Returns(blogsMock.Object);
        contextMock.Setup(m => m.Posts).Returns(postsMock.Object);

        _context = contextMock.Object;
        _controller = new BlogController(_context);
    }

    [Fact]
    public void Get_blog_1_returns_google() {
        // Act
        var result = _controller.GetBlog(1) as ViewResult;

        // Assert
        Assert.IsType(typeof(Blog), result.ViewData.Model);
        Blog model = (Blog)result.ViewData.Model;
        Assert.Equal("google", model.Url);
    }

}