【问题标题】:System.NotSupportedException : Unsupported expression: x => xSystem.NotSupportedException:不支持的表达式:x => x
【发布时间】:2020-12-27 07:27:42
【问题描述】:

我目前正在尝试最小化我的 Cafe Get 方法,如果找不到咖啡馆 ID,它将抛出 ArgumentNullexception

错误

System.NotSupportedException:不支持的表达式:x => x.Cafe 不可覆盖的成员(此处:Context.get_Cafe)不得用于设置/验证表达式。

发生这种情况是因为 moq 无法处理其中一个设置表达式吗?

单元测试

[Fact]
public async Task GetCafeByIdAsync_Should_Throw_ArgumentNullException()
{
    var cafe = new List<Cafe>()
    {
        new Cafe { Name = "Hanna", CafeId = 1},
        new Cafe { Name = "Bella", CafeId = 2 }
    }.AsQueryable();

    var mockSet = new Mock<DbSet<Cafe>>();
    mockSet.As<IQueryable<Cafe>>().Setup(m => m.Provider).Returns(cafe.Provider);
    mockSet.As<IQueryable<Cafe>>().Setup(m => m.Expression).Returns(cafe.Expression);
    mockSet.As<IQueryable<Cafe>>().Setup(m => m.ElementType).Returns(cafe.ElementType);
    mockSet.As<IQueryable<Cafe>>().Setup(m => m.GetEnumerator()).Returns(cafe.GetEnumerator());

    var mapper = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile(new AutoMapperProfile());
    }).CreateMapper();

    var contextMock = new Mock<Context>();
    contextMock.Setup(x => x.Cafe).Returns(mockSet.Object); //failing here

    var cafeService = new CafeService(contextMock.Object, mapper);

    await Assert.ThrowsAsync<ArgumentNullException>(() => cafeService.Get(2));
}

SUT

public async Task<VersionResponse> Get(int cafeId)
{
    var cafe = await _context.Cafe.Where(w => w.CafeId == cafeId).ToResponse().FirstOrDefaultAsync();
    return new VersionResponse()
    {
        Data = cafe
    };
}

【问题讨论】:

  • 我建议使用以下nuget packages 之一(取决于您的 EF 版本)。它使模拟变得更加容易。

标签: c# unit-testing moq


【解决方案1】:

Moq 依赖于能够创建覆盖属性的代理类。 Context.Cafe 不能被覆盖。尝试声明该属性virtual

public virtual IDbSet<Cafe> Cafe { get; set; }

【讨论】:

  • cafe 当前设置如下 -> public DbSet Cafe { get;放; } 将属性声明为虚拟时是否还需要 IDbSet?
  • 当我将其更改为虚拟时出现此错误 -> Castle.DynamicProxy.InvalidProxyConstructorArgumentsException:无法实例化类的代理:TestProject.Data.Context。找不到无参数构造函数。
  • @rey123:我不认为IDbSet&lt;&gt; 是必需的:我只是比你在这里发现的类更幸运地模拟接口。听起来您要么需要向Context 添加一个无参数构造函数,要么停止尝试模拟它。 :-) 考虑实现一个接口,然后模拟该接口。
  • 有没有其他方法可以测试公共异步任务 Get(int cafeId) - 因为它似乎正在使用上下文,所以认为最好尝试模拟上下文
  • 是的,如果_context 是一个注入接口(@98​​7654327@),并且.Cafe 在那个接口上,那么你应该可以模拟一个IContext
【解决方案2】:

尝试在 Set 方法上设置模拟

public virtual DbSet<TEntity> Set<TEntity>() where TEntity : class;

你的情况

var contextMock = new Mock<Context>();
contextMock.Setup(x => x.Set<Cafe>()).Returns(mockSet.Object);

【讨论】:

    【解决方案3】:

    我有一个更好的集成测试方法 您可以将事务回滚用于此类测试 这样你就可以在 Db 中做任何你想做的事情,然后 Db 中的每一个动作都会回到运行测试之前的最后状态

    如果您使用 Ef,您可以将此类添加到您的测试项目并享受它

    public abstract class PersistTest<T> : IDisposable where T : DbContext, new()
    {
        protected T DBContext;
        private TransactionScope scope;
        protected PersistTest()
        {
            scope = new TransactionScope();
            DBContext = new T();
        }
    
        public void Dispose()
        {
            scope.Dispose();
            DBContext.Dispose();
        }
    }
    

    这个类帮助你编写更好的集成测试,

    您的测试类应该继承自这个抽象类并将您的上下文类作为 DbContext 的实例传递

    关于 Codd 的另一件事是,您应该将 Logic 和 DataAccess 彼此分开

    只需定义一个名为 ICofeeRepository 的接口并将您的方法放入其中 你的代码打破了单一职责原则

    你的服务应该是这样的

    public class CoffeService
    {
        //inject cafe repository ; _repo
        public async Task<VersionResponse> Get(int cafeid)
        {
            var cafe = _repo.FindById(cafeid);
            if (cafe == null)
                throw Exception("");
            //other wise .....
        }
    }
    

    你的仓库应该是这样的

    public interface ICafeRepository
    {
        Cafe Get(int cafeid);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-27
      • 1970-01-01
      • 2018-10-31
      相关资源
      最近更新 更多