提问人:Josh 提问时间:10/29/2014 最后编辑:Josh 更新时间:1/18/2023 访问量:1902
如何为依赖于 DbEntityEntry 的对象创建单元测试
How to create a Unit Test for an object that depends on DbEntityEntry
问:
我有以下帮助程序方法,该方法从 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。
答:
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
用于代替可读性AppendFormat
string.Format
- 单元测试对于该方法实际测试的内容具有更具描述性的名称
- 单元测试使用具有可读性约束
Assert.That
Contains.Substring
评论