Reputation: 8477
I have the following helper method, which takes the validation messages out of the DbEntityValidationException. We need this because the details of validation aren't added to the Exception by default.
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();
}
I've run into an issue trying to create Unit Test, specifically I can't create a DbEntityValidationResult because it required an instance of DbEntityEntry, which doesn't have a public constructor.
Here is broken Unit Test, it fails on creating the 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));
}
Note, DbEntityEntry doesn't implement an interface, so I can't use a mock/fake.
Upvotes: 14
Views: 1989
Reputation: 598
A possible solution might be to wrap the exception into something you have control over:
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);
}
By using the interface, you can create a test double and pass it to your logging method:
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();
}
Now you can use it in a test like this:
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; }
}
Of course this solution only tests whether the validation errors are composed into the string you're expecting.
To test the behaviour of ValidationErrorContainer
, I'd using an integration test that creates an in-memory database and triggers an expected exception.
Note: I took some liberties with naming and structuring of the code:
ComposeValidationErrors
, because it doesn't actually log a messageStringBuilder
uses AppendFormat
instead of string.Format
for readabilityAssert.That
with the Contains.Substring
constraint for readabilityUpvotes: 0
Reputation: 334
Some mock frameworks like Moq do method redirection allowing you to mock classes without interfaces. Doing its similar to http://www.codenutz.com/unit-testing-mocking-base-class-methods-with-moq/
With that said that is not a good programming practice since you will make your testing code dependent on that framework forever and you lose some of the design benefits of unit testing.
What you can do is write a proxy class to wrap your untestable object and add an interface on it then you use the proxy class every single time you would use the normal class.
Upvotes: 0