简体   繁体   English

EF Entry.State上的单元测试失败

[英]Unit test failing on EF Entry.State

Is it possible to unit test this? 有可能进行单元测试吗?

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();
    }
}

The [TestInitialize] adds 1 user to the repository [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);

and I'm trying to test with this 而我正试着用这个来测试

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);

    (...)
}

But it dies on 1st line of UpdateAsync. 但它死在UpdateAsync的第一行。 Is the test that is wrong or the UpdateAsync implementation? 测试是错误的还是UpdateAsync实现? Is there any way I can test it? 有什么方法可以测试吗?

Edit 编辑

I added as suggested by Belogix 我按照Belogix的建议添加了

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

That gets me closer, I think, but still have the non-virtual error: Invalid setup on a non-virtual member: x => x.Entry(It.IsAny()) 我认为这让我更接近,但仍然存在非虚拟错误:非虚拟成员上的设置无效:x => x.Entry(It.IsAny())

Best quote ever: "All problems in computer science can be solved by another level of indirection" - Butler Lampson. 有史以来最好的报价:“计算机科学中的所有问题都可以通过另一层次的间接解决” - 巴特勒兰普森。

It looks like this can't be tested directly without adding some additional abstraction. 看起来如果不添加一些额外的抽象就无法直接测试。 I had to refactor my UpdateAsync method this way 我必须以这种方式重构我的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;
}

And then update my test code in the Initialize 然后在Initialize中更新我的测试代码

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

My test then finally passes 我的测试终于过去了

[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");
}

The ChangeTracker in dbContext tracks changes and hold the entities that are changed. dbContext中的ChangeTracker跟踪更改并保留更改的实体。 So you can assert the changed entity is among them. 所以你可以断言改变后的实体就在其中。

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
        ));

For the property it would be something like this: 对于该属性,它将是这样的:

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
        ));

It looks like you have not stubbed your context correctly... I am not at a computer with Visual Studio so here is some pseudo code that should demonstrate what I mean. 看起来你没有正确地描述你的context ...我不是在使用Visual Studio的计算机上所以这里有一些伪代码应该证明我的意思。 Replace the IsAnything with either your mocking frameworks way of ignoring argument or actually the user if you want to handle different responses. 如果要处理不同的响应,请使用IsAnything框架替换参数或实际上是用户替换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]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM