Wednesday, April 23, 2014

Unit testing calls to base class

There is no reasonable way to test whether a method from base class was invoked in derived class. You need to redesign your code to get rid of inheritance so test it thoroughly.

Consider following code where we have a RequestError class responsible for processing that is common to all types of errors. Specialized classes, derive from RequestError and perform additional handling that is specific to the error they represent e.g. show message on dispatcher's console when unauthorized request is made, and use parent handling as well.

public class RequestError
{
  public virtual void Handle(Details requestDetails)
  {
    /*
      Perform logic common to all errors, e.g. logging
      * */
  }
}

public class UnauthorizedRequestError : RequestError
{
  public override void Handle(Details requestDetails)
  {
    ShowOnDispatcherConsole("Unathorized attempt from IP: " + requestDetails.IPAddress);
    base.Handle(requestDetails);
  }

  private void ShowOnDispatcherConsole(string message)
  {
    /*
      ...
    * */
  }
}


To refactor this, I identified all delegations to base class and then removed inheritance and injected dependency object through the constructor. I also extracted interface from this class so it's easier for mocking purposes. The UnauthorizedRequestError now is a kind of decorator around RequestError. The code and test now look like this:

public interface IRequestError
{
  void Handle(Details requestDetails);
}

public class RequestError : IRequestError
{
  public void Handle(Details requestDetails)
  {
    /*
      Perform logic common to all errors, e.g. logging
      * */
  }
}

public class UnauthorizedRequestError : IRequestError
{
  private readonly IRequestError _generalError;

  public UnauthorizedRequestError(IRequestError generalError)
  {
    _generalError = generalError;
  }

  public void Handle(Details requestDetails)
  {
    ShowOnDispatcherConsole("Unathorized attempt from IP: " + requestDetails.IPAddress);
    _generalError.Handle(requestDetails);
  }

  private void ShowOnDispatcherConsole(string message)
  {
    /*
      ...
    * */
  }
}


[Test]
public void ShouldDelegateErrorHandlingToWrappedError()
{
  //GIVEN
  IRequestError generalError = Substitute.For<IRequestError>();
  var unauthorizedError = new UnauthorizedRequestError(generalError);
  var details = new Details();

  //WHEN
  unauthorizedError.Handle(details);

  //THEN
  generalError.Received(1).Handle(details);
}

No comments:

Post a Comment