其他分享
首页 > 其他分享> > 验证EF6与SaveChanges上发生的验证

验证EF6与SaveChanges上发生的验证

作者:互联网

我找到了不错的文章,让我开始使用Moq:https://msdn.microsoft.com/en-us/data/dn314429.aspx对基于Entity Framework的应用程序进行单元测试

我遇到的问题是,Mock的SaveChanges方法似乎没有像通常那样触发ValidateEntity方法.我在EntityTypeConfiguration中配置的所有验证设置都没有作为DbEntityValidationException抛出.

例如,我的AddRoles_Fails_For_Empty_Name进行测试以确保该服务无法添加名称为空的角色.没有应用IsRequired()配置,或者没有调用ValidateEntity方法.我应该提到的是,如果我在Web应用程序中使用实际上下文,则它可以正常工作.

我在下面包括了一些相关的单元测试,DbContext和Service代码.

我做错了什么吗?有任何已知问题或解决方法吗?

角色数据库图

public class RoleMap : EntityTypeConfiguration<Role>
{
    public RoleMap()
    {
        ToTable("bm_Roles");
        HasKey(r => r.Id);
        Property(r => r.Name).IsRequired().HasMaxLength(100).HasIndex(new IndexAttribute("UX_Role_Name") { IsUnique = true });
        Property(r => r.Description).HasMaxLength(500);
    }
}

DbContext

public class BlueMoonContext : DbContext, IBlueMoonContext
{
    public BlueMoonContext() : base("name=BlueMoon")
    {

    }

    public DbSet<Role> Roles { get; set; }
    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Configurations.AddFromAssembly(typeof(BlueMoonContext).Assembly);
    }

    public void MarkAsModified<T>(T entity) where T : class
    {
        entity.ThrowIfNull("entity");
        Entry<T>(entity).State = EntityState.Modified;
    }

    protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
    {
        var result = base.ValidateEntity(entityEntry, items);

        if (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified)
        {
            // Perform validations that require database lookups
            if (entityEntry.Entity is Role)
            {
                ValidateRole((Role)entityEntry.Entity, result);
            }
            else if (entityEntry.Entity is User)
            {
                ValidateUser((User)entityEntry.Entity, result);                    
            }
        }

        return result;
    }

    private void ValidateRole(Role role, DbEntityValidationResult result)
    {
        if (role.Name.HasValue() && !Roles.NameAvailable(role.Name, role.Id))
        {
            result.ValidationErrors.Add(new DbValidationError("Name", "Already in use"));
        }
    }

    private void ValidateUser(User user, DbEntityValidationResult result)
    {
        if (user.UserName.HasValue() && !Users.UserNameAvailable(user.UserName, user.Id))
        {
            result.ValidationErrors.Add(new DbValidationError("UserName", "Already in use"));
        }
        if (user.Email.HasValue() && !Users.UserNameAvailable(user.UserName, user.Id))
        {
            result.ValidationErrors.Add(new DbValidationError("Email", "Already in use"));
        }
    }
}

开户服务

public class AccountService : BaseService, IAccountService
{
    private IPasswordHasher _passwordHasher;

    public AccountService(IBlueMoonContext context, IPasswordHasher passwordHasher) : base(context)
    {
        _passwordHasher = passwordHasher;
    }

    public ServiceResult CreateRole(Role role)
    {
        role.ThrowIfNull("role");
        Context.Roles.Add(role);
        return Save();
    }

    // Copied from base service class
    protected ServiceResult Save()
    {
        var result = new ServiceResult();
        try
        {
            Context.SaveChanges();
        }
        catch (DbEntityValidationException validationException)
        {

            foreach (var validationError in validationException.EntityValidationErrors)
            {
                foreach (var error in validationError.ValidationErrors)
                {
                    result.AddError(error.ErrorMessage, error.PropertyName);
                }
            }
        }
        return result;
    }
}

单元测试

[TestFixture]
public class AccountServiceTests : BaseTest
{
    protected Mock<MockBlueMoonContext> _context;
    private IAccountService _accountService;

    [TestFixtureSetUp]
    public void Setup()
    {
        _context = new Mock<BlueMoonContext>();

        var data = new List<Role>
        {
            new Role { Id = 1, Name = "Super Admin" },
            new Role { Id = 2, Name = "Catalog Admin" },
            new Role { Id = 3, Name = "Order Admin" }
        }.AsQueryable();

        var roleSet = CreateMockSet<Role>(data);
        roleSet.Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>(ids => data.FirstOrDefault(d => d.Id == (int)ids[0]));

        _context.Setup(m => m.Roles).Returns(roleSet.Object);
        // _context.Setup(m => m.SaveChanges()).Returns(0);
        _accountService = new AccountService(_context.Object, new CryptoPasswordHasher());
    }

    [Test]
    public void AddRole_Fails_For_Empty_Name()
    {
        var role = new Role { Id = 4, Name = "" };

        var result = _accountService.CreateRole(role);
        Assert.False(result.Success);
    }
}

解决方法:

SaveChanges是一个虚拟方法,这意味着您调用了伪方法.

您可以创建模拟CallBase = true,但这不是一个好主意(它错过了UT的主意):

_context = new Mock<BlueMoonContext>(){ CallBase = true };

上面的代码将对未明确设置的任何方法/属性使用BlueMoonContext的实际实现.

RoleMap负责您的数据库结构,您应该将其作为集成测试(与数据库)的一部分进行测试.

我认为您应该创建一个集成测试来验证您数据库的完整性(例如,覆盖RoleMap),并使用Throw设置创建一个UT来覆盖catch部分(这是您单元的一部分):

_contest.Setup(x => x.SaveChanges())
          .Throws(new DbEntityValidationException());

编辑以回答评论中的OP问题

不,您不必分离内置的验证,而必须创建另一个测试(集成测试).在此测试中,您将验证验证行为:插入一个非法实体,期望会引发异常(使用ExpectedExceptionAttribute),然后验证DB为空…使用此模式来应用此行为:

try
{
    \\...
    \\try to commit
}
catch(DbEntityValidationException ex)
{
    \\do some validation, then: 
    throw;\\for ExpectedExceptionAttribute
}

我查看了EntityTypeConfiguration的api,没有看到任何允许使用UT的联系人(除非您使用MsFakes,TypeMock Isolator之类的工具,否则无法验证是否调用了ToTable / HasKey / Property).在集成测试中,该类正在EntityFramework(属于BCL的一部分)内部使用,您不必验证EntityFramework是否正常工作,而是要验证自定义规则是否已集成并且可以按预期工作(在this answer中,您可以阅读不测试BCL类的原因.

因此,请在AccountService的UT中使用Moq.为BlueMoonContext和RoleMap(无Moq)创建集成测试.

顺便说一句,@ LadislavMrnka提供了interesting way to test(integration test) EntityTypeConfiguration

标签:unit-testing,moq,asp-net,c,entity-framework
来源: https://codeday.me/bug/20191119/2037212.html