【问题标题】:Unit test failing on EF Entry.StateEF Entry.State 上的单元测试失败
【发布时间】:2014-09-10 14:45:48
【问题描述】:

是否可以对此进行单元测试?

public class MyRepository<T> where T : IdentityUser, new()
{
   public async Task UpdateAsync(T user)
    {
        _context.Entry(user).State = EntityState.Modified;
        _context.Entry(user).Property("UserName").IsModified = false;
        await _context.SaveChangesAsync();
    }
}

[TestInitialize] 将 1 个用户添加到存储库

_user = new IdentityUser { Id = "70a038cdde40" };

IDbSet<IdentityUser> users = new FakeDbSet<IdentityUser> { _user };

var dbContext = new Mock<MyDbContext<IdentityUser>>();
dbContext.Setup(x => x.Users).Returns(() => users);

_repository = new MyRepository<IdentityUser>(dbContext.Object);

我正在尝试用这个进行测试

private MyRepository<IdentityUser> _repository;

[TestMethod]
public async Task UpdateUser_Success2()
{
    var user = await _repository.FindByIdAsync("70a038cdde40");
    Assert.IsFalse(user.EmailConfirmed, "User.EmailConfirmed is True");

    user.EmailConfirmed = true;

    await _repository.UpdateAsync(user);

    (...)
}

但它在 UpdateAsync 的第一行就死了。是测试错误还是 UpdateAsync 实现?有什么办法可以测试吗?

编辑

我按照 Belogix 的建议添加了

 dbContext.Setup(x => x.Entry(It.IsAny<IdentityUser>()))
                       .Returns(() => dbContext.Object.Entry(_user));

我认为这让我更接近,但仍然有非虚拟错误:非虚拟成员上的无效设置:x => x.Entry(It.IsAny())

【问题讨论】:

  • 在您运行测试时,_context 是什么?模拟的东西还是真实的东西(指向数据库)?您是否对相关部分进行了存根处理,以便对其进行调用?
  • @Belogix 我已经更新了我的问题,希望它能回答你的问题
  • 不,关闭,但是如果您查看我的答案,则需要模拟 Entry 以返回 user 而不是 State 因为这是您班级的财产...所以使用我的模拟示例Entry 再试一次……这样你就可以在那个模拟对象上设置状态等。希望这是有道理的!
  • 我确实尝试过,但返回 users[0] 不起作用,因为它无法将索引应用于类型 IDbSet 和 users.First 也不起作用,因为 IdentityUser 不能被转换为 DbEntityEntry
  • Entry返回的类型必须是DbEntityEntry类型,_user就是IdentityUser,懂我的意思吗?抱歉,我想我误删了你的评论。

标签: c# entity-framework unit-testing


【解决方案1】:

有史以来最好的名言:“计算机科学中的所有问题都可以通过另一种间接方式来解决”- Butler Lampson。

看起来如果不添加一些额外的抽象就无法直接测试。我不得不以这种方式重构我的 UpdateAsync 方法

public async Task UpdateAsync(T user)
{
    SetEntityStateModified(user);
    SetPropertyIsModified(user);
    await _context.SaveChangesAsync();
}

public virtual void SetPropertyIsModified(T user)
{
    _context.Entry(user).Property("UserName").IsModified = false;
}

public virtual void SetEntityStateModified(T user)
{
    _context.Entry(user).State = EntityState.Modified;
}

然后在 Initialize 中更新我的测试代码

_repository = new Mock<MyRepository<IdentityUser>>(dbContext.Object);
_repository.Setup(x => x.SetEntityStateModified(It.IsAny<IdentityUser>()));
_repository.Setup(x => x.SetPropertyIsModified(It.IsAny<IdentityUser>()));

然后我的测试终于通过了

[TestMethod]
public async Task can_update_user_details()
{
    //Arrange
    var user = await _repository.Object.FindByIdAsync("70a038cdde40");
    Assert.IsFalse(user.EmailConfirmed, "User.EmailConfirmed is True");

    //Act            
    user.EmailConfirmed = true;

    await _repository.Object.UpdateAsync(user);
    var newUser = await _repository.Object.FindByIdAsync("70a038cdde40");

    //Assert
    Assert.IsTrue(newUser.EmailConfirmed, "User.EmailConfirmed is False");
}

【讨论】:

    【解决方案2】:

    dbContext 中的 ChangeTracker 跟踪更改并保存已更改的实体。所以你可以断言改变的实体就在其中。

    Assert.IsTrue(dbContext.Object.ChangeTracker.Entries().Any(entry =>
                entry.State == EntityState.Modified &&
                entry.Entity is IdentityUser &&
                (entry.Entity as IdentityUser).Id == users[0].Id // Here you can check if it is actually the same user
            ));
    

    对于属性,它会是这样的:

    Assert.IsTrue(_context.Object.ChangeTracker.Entries().Any(entry =>
                entry.Property("UserName").IsModified == false &&
                entry.Entity is IdentityUser &&
                (entry.Entity as IdentityUser).Id == users[0].Id // Here you can check if it is actually the same user
            ));
    

    【讨论】:

      【解决方案3】:

      看起来你没有正确地存根你的context...我不在使用 Visual Studio 的计算机上,所以这里有一些伪代码应该可以证明我的意思。如果您想处理不同的响应,请将 IsAnything 替换为忽略参数的模拟框架方式或实际用户。

      // Existing context initialisation...
      var dbContext = new Mock<MyDbContext<IdentityUser>>();
      dbContext.Setup(x => x.Users).Returns(() => users);
      
      // NEW: Mock what / how Entry is going to return when called (i.e. return a user)
      dbContext.Setup(x => x.Entry(IsAnything)).Returns(() => users[0]);
      

      【讨论】:

      • 这不起作用,因为Entry在EntityFramework中不是虚拟的,因此不可模拟
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-17
      • 2014-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多