Why Use A Dummy Exception?
At the end of Today’s Tip, I’ll pose a question to you. I’ll reveal the answer tomorrow. As with all my blog articles, my aim is for you to gain deep insights into systems design and architecture.
Here is the system outline.
Below is the code for a .NET WebApi CustomerController. It has a single controller action, Register():
public class CustomerController : ApiController { public IRegisterCustomerUseCase RegisterUseCase { get; private set; } public CustomerController(IRegisterCustomerUseCase registerUseCase) { RegisterUseCase = registerUseCase; } [HttpPost] public async Task<IHttpActionResult> Register(ApiCustomerRegistration customerReg) { try { Validate(customerReg); var reg = customerReg.ToRegistration(); var customer = await RegisterUseCase.Register(reg); return Ok(customer); } catch (Exception ex) { return HandleException(ex); } } private void Validate(ApiCustomerRegistration customerReg) { if (customerReg == null) throw new MissingCustomerRegistration(); } private IHttpActionResult HandleException(Exception ex) { if (ex is ClientInputException) return BadRequest(ex.Message); return InternalServerError(); } }
The code is easy to follow. The business logic behaviour doing the heavy lifting appears to be delegated to the Register() method of the RegisterCustomerUseCase class. The code in RegisterCustomerUseCase.Register(), which is not shown, performs validation on the customer registration data passed into it. Understand that if the data fails validation, Register() will throw exceptions derived from ClientInputException. For example:
public class MissingEmailAddress : ClientInputException { public MissingEmailAddress() : base("Missing email address.") { } }
ClientInputException is an abstract exception type:
public abstract class ClientInputException : Exception { public ClientInputException(string message) : base(message) { } }
ClientInputException exists to group exceptions of a similar cause together, as described here.
The controller action also throws a ClientInputException from within its own Validate() method, MissingCustomerRegistration exception:
private void Validate(ApiCustomerRegistration customerReg) { if (customerReg == null) throw new MissingCustomerRegistration(); }
where
public class MissingCustomerRegistration : ClientInputException { public MissingCustomerRegistration() : base("Missing customer registration data.") { } }
Now, the Register() controller action handles exceptions via the HandleException method sitting inside the catch block:
catch (Exception ex) { return HandleException(ex); }
where HandleException is defined as
private IHttpActionResult HandleException(Exception ex) { if (ex is ClientInputException) return BadRequest(ex.Message); return InternalServerError(); }
The only relevant part is that HandleException() emits an HTTP 400-BadRequest status when the exception is a ClientInputException—which makes sense.
So far, so good.
Now, there exists a unit test for the exception catching and handling behaviour of the Register() controller action:
[Theory] [InlineData("Client Input Error")] [InlineData("a different error message")] public async Task Given_UseCase_Throws_ClientInputException_When_Call_Register_Then_Return_400_BadRequest(string errorMsg) { var controller = SetupController(new DummyClientInputException(errorMsg)); var result = await controller.Register(ApiRegoAdamAnt); VerifyBadRequestResult(result, errorMsg); }
SetupController() prepares a stubbed version of the RegisterCustomerUseCase to throw the passed-in DummyClientInputException.
public class DummyClientInputException : ClientInputException { public DummyClientInputException(string message) : base(message) { } }
Here is my question to you: Why is this unit test using a DummyClientInputException rather than a real ClientInputException that the actual RegisterCustomerUseCase.Register() or the Validate() method of the Controller action would throw? In other words, why not use a MissingCustomerRegistration exception or MissingEmailAddress exception? Why is it preferable to use a dummy exception that will never be thrown when the code is deployed?
Please hit reply and send in your answers – you’ll learn more if you do. And it doesn’t matter if you get it wrong.
I’ll publish the answer tomorrow.
Leave a Reply
Want to join the discussion?Feel free to contribute!