Testing error handling in RESTful application with jersey and jbehave

Hi reader,

As far as I’m concerned I write tests mostly for one reason : deal with the non-nominal path because in most cases the nominal path is the easier one. They help me dealing with the question What if the user/client doesn’t provide correct inputs how will my application behave ?

RESTful applications are no exceptions. How do we manage non-nominal cases ?
As REST is built on top of HTTP a great amount of work has already been done with error semantics.
Still, that semantic will never match your application’s. Neither will it match your platform native error mechanism.
It’s a classic issue that I want to address for RESTful applications.

 

1. Carefully design error mapping based on error semantics

 

In HTTP error semantics give an indication on the error type. They are represented by status codes.
That system helps categorizing responses and specially errors. For example all 4XX status codes are considered errors in HTTP and 404 are “Not found” errors.
It can lead us to imagine a simple yet already powerful java-http error mapping :

  • Bad request (400) : IllegalArgumentException. I usually throw this exception when the user input doesn’t allow me to correctly process the request; be it a wrong data format (null, empty, incorrect date pattern, etc) or an unsatisfied business rule
  • Bad request (400) : ValidationException. Thrown by jee validator
  • Authentication required (401) : AuthenticationException. Thrown by spring security when bad credentials are provided
  • Access denied (403) : AccessDeniedException. Thrown by spring security when good credentials are provided but user doesn’t have enough rights for this request
  • Not found (404) : null result on “find by id” requests. Not to be confused with empty search result which should return 200 and empty results
  • Internal server error (500) : IllegalStateException. The container returns a 500 by default (unhandled application errors) but an IllgalStateException should also lead to a 500 because the system state is instable

The above mapping can be refined as needed. But the idea is getting most of what already exists. Avoid creating ad-hoc exceptions.
 

2. Provide the correct error context via messages

 

The status code is fine but not enough because one status code corresponds to many contexts. I can throw a 404 for many reasons in my app. This is where messages come into play.
The message holds more context. A 404 error with this message “Product with reference ref-55555-ermcccvpsole-59999 was not found.” is much clearer and carries more valuable information than this one “404 – Not found”.
 

3. Implement i18n to improve your service

 

Sometimes you have to internationalize your application. HTTP defines a Accept-Language header which allows a client to specify its preferred response language. When the specified language is not supported by the server a good practice is to provide a fallback one.
Technically it’s supposed to be as simple as using properties files.
Things get rough when it comes to using UTF-8 encoding (the properties default encoding is ISO 8859-1) … They get even harder when some messages thrown by a third-party framework reaches the end user. Most time those messages are technical, and if you’re lucky enough they are in the same language as your application (which is unlikely).
 

4. Let’s get to work !

 

As usual, a simple feature gets richer and could use a set of scenarii. Here is my expected specification in natural language :

  • Different semantics should return different http codes
  • Same semantic with different contexts should return same http code and different messages
  • Same semantic with same context and different supported languages should return same http code and different messages
  • Same semantic with same context and different unsupported languages should return same http code and same messages

You know the rules (you can explore the code to understand jbehave configuration) : write plain text story, code story implementation, note the failure and change your application behaviour until story is done. Repeat this whenever you want to ensure/proove a behaviour. At the end you should get quite a covered software.

Plain text story : nothing new here, same old given/when/then.

...
Scenario: Same semantic with same context and different supported languages should return same http code and different messages
Given I receive  data
Given I accept  language
When I send a find advert by reference with reference .
Then I should get an unsuccessful response
And the response code should be 404
And the response message should be

Examples:
|responseContentType|responseLanguage|reference|message
|application/json|en|ref-55555-ermcccvpsole-59999|Advert with reference ref-55555-ermcccvpsole-59999 was not found
|application/json|fr|ref-55555-ermcccvpsole-59999|La référence ref-55555-ermcccvpsole-59999 est inconnue
...

Story implementation : constructs the http message (uri, headers, body), sends it and reads the response for assertions. We could use many http clients but why bother when jersey client suits ?

...
	@Given("I accept  language")
	public void setAcceptLanguage(@Named("responseLanguage") final String responseLanguage) {
		this.responseLanguage = responseLanguage;
	}

	@Given("I receive  data")
	public void setResponseContentType(@Named("responseContentType") final String responseContentType) {
		this.responseContentType = responseContentType;
	}
	@When("I send a find advert by reference with reference .")
	public void sendFindByReferenceRequest(@Named("reference") final String reference) {
		final String path = "/advert/find/reference/" + reference;
		final URI uri = URI.create(this.baseEndPoint + path);
		final DefaultClientConfig config = new DefaultApacheHttpClient4Config();
		config.getClasses().add(JacksonJsonProvider.class);
		final Client jerseyClient = ApacheHttpClient4.create(config);
		jerseyClient.addFilter(new LoggingFilter());
		this.response = jerseyClient.resource(uri).accept(MediaType.valueOf(this.responseContentType))
				.acceptLanguage(new String[] { this.responseLanguage }).get(ClientResponse.class);
	}
	@Then("the response message should be ")
	public void expectResponseMessage(@Named("message") final String responseMessage) {
		ResponseError error = this.response.getEntity(ResponseError.class);
		Assert.assertEquals(responseMessage, error.getMessage());
	}

	@Then("the response code should be $statusCode")
	public void expectStatusCode(@Named("statusCode") final int statusCode) {
		Assert.assertEquals(statusCode, this.response.getStatus());
	}

	@Then("I should get an unsuccessful response")
	public void responseShouldBeUnsuccessful() {
		int responseStatus = this.response.getStatus();
		final int statusCodeFirstDigit = Integer.valueOf(String.valueOf(responseStatus).substring(0, 1));
		Assert.assertTrue(statusCodeFirstDigit != 2 && statusCodeFirstDigit != 3);
	}
...

Then you can implement the expected behaviour (error handling in our case).
First Controller

...
	@GET
	@Path("/find/reference/{reference}")
	@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
	public Response findByReference(@PathParam(value = "reference") final String reference) throws Throwable {

		final Advert criteria = new Advert();
		criteria.setReference(reference);

		final List results = this.facade.findAdvertsByCriteria(criteria);

		if (CollectionUtils.isEmpty(results)) throw new NotFoundException(new Object[] { reference });

		return Response.ok(results.get(0)).build();

	}
...

Then ExceptionMapper : @Provider automatically registers this bean as a jersey special bean. Implementing ExceptionMapper marks this bean as an exception handler. This is where you can build your response : status, body, cookies, headers, whatever pleases you. This really is priceless to me. It allows me to send custom error reponses to client (complex message structure instead of just a string, complex object type instead of plain text when needed, etc). It’s all well documented here.

...
@Component
@Provider
public class GenericExceptionMapper implements ExceptionMapper {
	@Autowired
	private LocaleResolver		localeResolver;
	@Context
	private HttpServletRequest	request;

	/**
	 * @see javax.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable)
	 */
	@Override
	public Response toResponse(Throwable th) {
		final ResponseError error = resolve(th, this.request);
		return Response.status(error.getHttpStatus()).entity(error).build();
	}

	private Locale getLocale(final HttpServletRequest request) {
...
	}

	public ResponseError resolve(final Throwable th, final HttpServletRequest request) {
...
	}

	int resolveHttpStatus(final Throwable th) {
...
	}

	String resolveMesage(final HttpServletRequest request, final Throwable th) {
...
	}

}

That’s it, you’re done. You can note that the difficult part is analyzing and expressing what’s expected. This is where a tool like jbehave is very handy.

Here is a report screenshot :

 

 
 
 
 
 

 

 
 

Run firefox target/jbehave/view/fr.explorimmo.stories.ErrorHandling.html to view the complete report.

I pushed the complete source on github.

Feel free to fork, tweet, comment, react share or whatever pleases you as soon as it remains constructive.

Cheers.

Advertisement

One thought on “Testing error handling in RESTful application with jersey and jbehave

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s