【问题标题】:NSubstitute, DbContext and a strange issue when trying to write a test尝试编写测试时的 NSubstitute、DbContext 和一个奇怪的问题
【发布时间】:2025-12-30 19:30:16
【问题描述】:

我有一个很好的扩展方法来模拟 DbSet

public static class DbSetExtensions
{
    public static DbSet<T> ToDbSet<T>(this IEnumerable<T> data) where T : class
    {
        var queryData = data.AsQueryable();
        var dbSet = Substitute.For<DbSet<T>, IQueryable<T>>();
        ((IQueryable<T>)dbSet).Provider.Returns(queryData.Provider);
        ((IQueryable<T>)dbSet).Expression.Returns(queryData.Expression);
        ((IQueryable<T>)dbSet).ElementType.Returns(queryData.ElementType);
        ((IQueryable<T>)dbSet).GetEnumerator().Returns(queryData.GetEnumerator());

        return dbSet;
    }
}

我试图在这样的上下文文件中使用它:

public class DatabaseContextContext<T> where T: DatabaseContextContext<T>
{
    public DatabaseContext DatabaseContext;
    protected DatabaseContextContext()
    {
        DatabaseContext = Substitute.For<DatabaseContext>();
    }

    public T WhenListSucceeds<TEntity>(IList<TEntity> data) where TEntity : class
    {
        var dbSet = data.ToDbSet();
        DatabaseContext.Set<TEntity>().Returns(dbSet);

        return (T)this;
    }

    public T WhenGetSucceeds<TEntity>(TEntity entity) where TEntity : class
    {
        var dbSet = new List<TEntity> { entity }.ToDbSet();
        DatabaseContext.Set<TEntity>().Returns(dbSet);

        return (T)this;
    }
}

当我在这个方法上运行我的测试时,它失败了:

public ActionResult<List<Formula>> ListFormulas(int id) =>
    Ok(_databaseContext.Formulas.Where(m => m.AttributeId.Equals(id)).ToList());

带有此错误消息:

System.InvalidCastException:无法将“Castle.Proxies.ObjectProxy_3”类型的对象转换为“Microsoft.EntityFrameworkCore.Metadata.Internal.Model”类型。

所以我试着把它分解一下。 首先,我将方法更改为:

public ActionResult<List<Formula>> ListFormulas(int id)
{
    var s = _databaseContext.Formulas;
    var x = _databaseContext.Formulas.ToList();
    var t = _databaseContext.Formulas.Where(m => m.AttributeId.Equals(id)).ToList();

    return Ok(t);
}

但是在调试时,代码没有通过ToList() 方法。我仍然遇到同样的问题。所以我把我的代码改成了这样:

public ActionResult<List<Formula>> ListFormulas(int id)
{
    var p = _databaseContext.Set<Formula>();
    var q = p.ToList();


    var s = _databaseContext.Formulas;
    var x = _databaseContext.Formulas.ToList();
    var t = _databaseContext.Formulas.Where(m => m.AttributeId.Equals(id)).ToList();

    return Ok(t);
}

前 3 行代码有效,但一旦到达var x = _databaseContext.Formulas.ToList(); 行,它就会失败。 有人知道为什么吗?

这是测试:

[TestFixture]
public class ListShould
{
    [Test]
    public void ReturnList()
    {
        // Assemble
        var services = GenericOrderProviderContext.GivenServices();
        var provider = services.WhenCreateOrderProvider();

        services.DatabaseContext.Attributes = new List<Attribute>().ToDbSet();
        services.DatabaseContext.Set<Attribute>().ReturnsForAnyArgs(_ => new List<Attribute>().ToDbSet());

        // Act
        var result = provider.List();

        // Assert
        result.Failure.Should().BeFalse();
        result.Result.Count().Should().Be(0);
    }
}

【问题讨论】:

  • 最好使用内存数据库。
  • 另外,DatabaseContext.Set&lt;TEntity&gt;().Returns(dbSet) 仅在调用 .Set&lt;TEntity&gt;() 时有效。在您的示例中,当模拟未知 _databaseContext.Formulas
  • 我也是这么想的,但是我将 dbSet 硬编码为公式,仍然得到同样的错误

标签: c# unit-testing dbcontext nsubstitute


【解决方案1】:

当未配置 db 上下文 .Formulas 属性时,我能够重现您的错误。如果您同时使用.Set&lt;Formula&gt;().Formulas,则需要同时配置两者。

我确实注意到您对 db set 枚举器的设置

((IQueryable<T>)dbSet).GetEnumerator().Returns(queryData.GetEnumerator());

导致我之前看到的一些行为,其中只有第一个 ToList() 调用返回结果。如果你明白了,你可能需要重置枚举器或使用 Func&lt;CallInfo, IEnumerator&lt;Formula&gt;&gt; Returns 重载。

【讨论】:

  • 我已经设置了这两个并且我的第一个调用 .ToList() 是在那个测试中,所以它不应该是 GetEnumerator 的问题
  • 弹出一个最小可复现的例子,我再看看