Josh
Josh

Reputation: 8477

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

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

Answers (2)

G_hi3
G_hi3

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:

  • I renamed the method ComposeValidationErrors, because it doesn't actually log a message
  • StringBuilder uses AppendFormat instead of string.Format for readability
  • Unit test has a more descriptive name for what the method should actually test
  • Unit test uses Assert.That with the Contains.Substring constraint for readability

Upvotes: 0

Alfredo Alvarez
Alfredo Alvarez

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

Related Questions