【问题标题】:How to setup Mocked context using Moq?如何使用 Moq 设置模拟上下文?
【发布时间】:2026-02-09 09:05:02
【问题描述】:

我已经使用洋葱架构和数据访问实体框架实现了存储库模式,现在我想使用 Moq 对其进行测试。我刚刚问了一个关于 SO 的问题,而答案让我现在更加困惑(答案很好,但即使在阅读了文档之后,我对如何模拟的理解也很差)。我想做的是测试Repository 方法Get(long id)。我的存储库构造函数将DbContext 作为参数(称为PrincipalServerContext,因此建议我模拟上下文以测试我的Repository。假设这是我的Repository

public class PrincipalServerContext : DbContext
{
    public DbSet<Web_Documents> WebDoc { get; set; }
    public PrincipalServerContext()
        : base("name=PrincipalServerDB")
    {
        Database.SetInitializer(new Initializer());
    }
 }

现在是我的 POCO 之一Web_Documents(EF 实体):

public class Web_Documents
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long IDDocument { get; set; }

    [Required]
    [MaxLength(255)]
    public string NomDocument { get; set; }

    [Required]
    public long IDCategorie { get; set; }

    [ForeignKey("IDCategorie")]
    public Web_Categories cat { get; set; }
    [Required]
    [MaxLength(255)]
    public string Lien { get; set; }

    [MaxLength(50)]
    public string Type { get; set; }

    public virtual ICollection<Web_Profils> Profils { get; set; } 
}

最后是我的Repository 方法(知道存储库是通用的,我使用 POCO 作为通用类型):

public T Get(long id)
{
    ObjectContext objContext = ((IObjectContextAdapter)context).ObjectContext;
    ObjectSet<T> set = objContext.CreateObjectSet<T>();
    IEnumerable<string> keyNames = set.EntitySet.ElementType
                                                .KeyMembers
                                                .Select(k => k.Name);
    if (keyNames.Count() > 1)
        return null;
    else
    {
        string idName = keyNames.ElementAt(0); // For Document would be IDDocument
        var parameter = Expression.Parameter(typeof(T));
        var property = Expression.Property(parameter, idName);
        var idValue = Expression.Constant(id, id.GetType());
        var equal = Expression.Equal(property, idValue);
        var predicate = Expression.Lambda<Func<T, bool>>(equal, parameter);
        return entities.SingleOrDefault(predicate);
        //Returns the corresponding entity to 'id' and 'T'
    }
}

这会构建一个具有适当 ID 名称的表达式,因为每个表都有不同的 ID 名称(公司政策)。

从这里Should this case of Assert.AreSame return true? 被告知的内容,我知道我必须为 Mock 对象构建一个返回类型,但是我的上下文类太薄了,我没有任何方法或任何东西,只有一个 DbSet。 所以我尝试了这个作为测试,但它可能没有任何意义,因为它失败了(我真的迷路了,不明白):

Mock<PrincipalServerContext> moqContext;
public void IdExists(){
    moqContext = new Mock<PrincipalServerContext>();
    var set = new Mock<DbSet<Web_Documents>>();
    moqContext.Setup(c => c.Set<Web_Documents>()).Returns(set.Object);
    repoDoc = new Repository<Web_Documents>(moqContext.Object);
    var testDoc = repoDoc.Get(1L);
    Assert.AreEqual(testDoc.NomDocument, "Ajouter une catégorie");
}

假设我想做一个简单的测试来查找搜索的 ID 是否与我的数据库条目相对应,我应该如何设置我要定义的 moqContext 对象?在示例中,我看到他们通常有用于模拟对象的方法,但这里没有,所以我找到了 Mocking DbContext for TDD Repository 这让我尝试了这个测试。

感谢您的帮助!

【问题讨论】:

  • 考虑让您的存储库保持精简并在该级别进行模拟。如果存储库没有业务逻辑,那么它可以作为单元测试的一个很好的切入点。这是我使用存储库模式的理由,所以我不必尝试模拟上下文。使用集成(端到端)测试来断言对于已知数据状态,您的代码按预期运行。这样,单元测试可以快速运行 (TDD),而集成测试仍然覆盖您的端到端。
  • @StevePy 那么你是说我不需要模拟我的上下文来测试这些方法,而只需进行经典的单元测试?对于集成测试,我将在使用存储库模式的 MVC 项目中测试控制器,但现在我仍然想确保一切正常
  • 如果存储库很薄,因为它们只是根据简单的标准返回实体或保存/删除这些实体,那么它们不会从单元测试中受益匪浅。例如通过 ID “获取”,没有逻辑,它是对 ORM 的传递。您可能想要在存储库上测试的是存储库是否添加了任何较低级别的标准,例如授权检查等。这些是集成运行期间可以涵盖的内容。真正的逻辑在控制器和服务中,模拟存储库使这些类更容易测试,您将在其中经常测试它们。
  • 我找到了 EF Core 团队最近提供的关于在模拟上下文时使用内存数据库的指导,我成功了。使用此方法,您可以创建一个使用内存数据库的模拟上下文,然后您可以将数据播种到上下文中的不同 DbSet 中。然后,您可以传递该上下文并针对该上下文执行查询。如果您有兴趣,我会花时间举例说明。
  • @MORCHARD 是的,肯定有兴趣举个例子!!谢谢!

标签: c# .net entity-framework moq repository-pattern


【解决方案1】:

这是使用内存数据库的示例。

首先,您创建一个请求模拟工作单元的实例。

[TestMethod]
public async Task ExampleTest() {
    //arrange
    Mock<IUnitOfWork> mockUow = MockUowFactory.Get(nameof(ExampleTest));

    //act
    using (var app = YOURAPP(mockUow.Object)){
         app.METHODUNDERTEST();
    }

    //assert
    ...
}

然后构建模拟工作单元。根据我读过的内容,需要单独的上下文(一个用于播种,一个用于测试)。 MockEntityFactory 只返回一个虚拟数据数组,用于填充我们的 InMemoryDatabase 中的 dbset。

    public class MockUowFactory {

    public static Mock<IUnitOfWork> Get(string dbName) {

        DbContextOptions<YOUR CONTEXT> options = new DbContextOptionsBuilder<YOUR CONTEXT>()
            .UseInMemoryDatabase(databaseName: dbName)
            .Options;


        using (var seedContext = new YOURCONTEXT(options)) {

            seedContext.YOURENTITY.AddRange(MockEntityFactory.YOURENTITY);

            seedContext.SaveChanges();
        }

        var context = new YOURCONTEXT(options);
        var mockUow = new Mock<IUnitOfWork>();
        mockUow.Setup(m => m.Context).Returns(context);
        mockUow.Setup(m => m.Save()).Returns(() => context.SaveChanges().ToString());

        return mockUow;
    }
}

然后我通过必要的层传递这个工作单元,并且不需要做任何特别的事情来测试我的生产代码。

【讨论】: