如何为依赖于 DbEntityEntry 的对象创建单元测试

How to create a Unit Test for an object that depends on DbEntityEntry

提问人:Josh 提问时间:10/29/2014 最后编辑:Josh 更新时间:1/18/2023 访问量:1902

问:

我有以下帮助程序方法,该方法从 DbEntityValidationException 中取出验证消息。我们需要这样做,因为默认情况下不会将验证的详细信息添加到异常中。

public static string LogMessageDbEntityValidationException(DbEntityValidationException ex)
{
   StringBuilder error = new StringBuilder();

   error.AppendLine("Validation Error details for DbEntityValidationException throw: ");

   foreach (var validationErrors in ex.EntityValidationErrors)
   {
      foreach (var validationError in validationErrors.ValidationErrors)
      {
         error.AppendLine(string.Format("Property: {0} , Error: {1}", 
                          validationError.PropertyName, validationError.ErrorMessage));
      }
   }

   return error.ToString();
}

我在尝试创建单元测试时遇到了一个问题,特别是我无法创建 DbEntityValidationResult,因为它需要一个没有公共构造函数的 DbEntityEntry 实例。

这是损坏的单元测试,它在创建 DbEntityEntry 时失败:

public void LogMessageDbEntityValidationExceptionTest()
{
  string errorMessage = "Unit Test Error Message";
  string expected = "Not valid data.";
  List<DbEntityValidationResult> entityValidationResults = new List<DbEntityValidationResult>();
  List<DbValidationError> errorList = new List<DbValidationError>();
  DbEntityValidationException ex;

  errorList.Add(new DbValidationError("TestProperty", expected));

  entityValidationResults.Add(new DbEntityValidationResult(new System.Data.Entity.Infrastructure.DbEntityEntry(), errorList));

  ex = new DbEntityValidationException(errorMessage, entityValidationResults);
  string actual = Common.LogMessageDbEntityValidationException(ex);

  Assert.IsTrue(actual.Contains(expected));
}

请注意,DbEntityEntry 没有实现接口,所以我不能使用 mock/fake。

C# .NET 实体框架 单元测试 mstest

评论

0赞 Kurt Koller 11/17/2016
你有没有找到解决这个问题的方法?

答:

0赞 Alfredo Alvarez 10/29/2014 #1

一些模拟框架(如 Moq)可以进行方法重定向,从而允许您模拟没有接口的类。做类似于 http://www.codenutz.com/unit-testing-mocking-base-class-methods-with-moq/

话虽如此,这不是一个好的编程实践,因为你会让你的测试代码永远依赖于该框架,并且你失去了单元测试的一些设计优势。

你可以做的是编写一个代理类来包装你的不可测试对象,并在其上添加一个接口,然后你每次使用普通类时都使用代理类。

评论

2赞 Josh 10/29/2014
不幸的是,不可测试的对象被埋没在 EF 代码中。我必须为太多的代码编写一个包装器。
0赞 Alfredo Alvarez 10/29/2014
还可以尝试使用对象初始值设定项来调用对象的私有默认构造函数。msdn.microsoft.com/en-us/library/bb397680.aspx话虽如此,使用 Moq,您应该能够使用框架来做到这一点。
0赞 G_hi3 1/18/2023 #2

一个可能的解决方案可能是将异常包装到您可以控制的内容中:

public interface IValidationErrorContainer
{
    IEnumerable<DbValidationError> ValidationErrors { get; }
}

public class ValidationErrorContainer : IValidationErrorContainer
{
    private readonly DbEntityValidationException _exception;

    public ValidationErrorContainer(DbEntityValidationException exception)
    {
        _exception = exception;
    }

    public IEnumerable<DbValidationError> ValidationErrors
        => _exception.EntityValidationErrors.SelectMany(validationError => validationError.ValidationErrors);
}

通过使用该接口,您可以创建一个测试替身并将其传递给您的日志记录方法:

public string ComposeValidationErrors(IValidationErrorContainer container)
{
    var error = new StringBuilder(
        "Validation Error details for DbEntityValidationException throw:");

    foreach (var validationErrors in container.ValidationErrors)
    {
        error.AppendFormat(
            "\nProperty: {0}, Error: {1}",
            validationError.PropertyName,
            validationError.ErrorMessage);
    }

    return error.ToString();
}

现在,您可以在如下测试中使用它:

public void ComposeValidationErrors_ReturnsTextContainingExpectedSubstring()
{
    var expected = "Not valid data.";
    var container = new FakeValidationErrorContainer
    {
        ValidationErrors = new[] { new DbValidationError("TestProperty", expected) }
    };
    var actual = Common.ComposeValidationErrors(container);
    Assert.That(actual, Contains.Substring(expected));
}

private class FakeValidationErrorContainer : IValidationErrorContainer
{
    public IEnumerable<DbValidationError> ValidationErrors { get; set; }
}

当然,此解决方案仅测试验证错误是否已组合到您期望的字符串中。 为了测试 的行为,我将使用集成测试来创建内存数据库并触发预期的异常。ValidationErrorContainer

注意:我在代码的命名和结构上采取了一些自由:

  • 我重命名了该方法,因为它实际上并没有记录消息ComposeValidationErrors
  • StringBuilder用于代替可读性AppendFormatstring.Format
  • 单元测试对于该方法实际测试的内容具有更具描述性的名称
  • 单元测试使用具有可读性约束Assert.ThatContains.Substring