Skip to content

Commit

Permalink
Add hasContentType matcher, HTTP status consts
Browse files Browse the repository at this point in the history
This matcher removes a lot of boilerplate from checking the Content-Type
of a HttpResponse. It's based on the Hamcrest API with the help of these
tutorials:

- https://hamcrest.org/JavaHamcrest/tutorial
- https://www.baeldung.com/hamcrest-custom-matchers

This could be generalized to handle any header.  However, I want to keep
it straightforward for now, as a good basic example of how to write a
custom matcher.

Also took the opportunity to replace bare HTTP status codes (200, et.
al.) with constants defined on jakarta.servlet.http.HttpServletResponse.
  • Loading branch information
mbland committed Nov 25, 2023
1 parent 85754d5 commit b987997
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 10 deletions.
@@ -0,0 +1,40 @@
package com.mike_bland.training.testing.matchers;

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

import java.net.http.HttpResponse;

// Custom Hamcrest matcher validating the Content-Type header of a HttpResponse.
//
// This could be generalized to check any HTTP header, or collection thereof.
// However, since this is a teaching example, we'll keep it straightforward.
class HasContentType<T> extends TypeSafeMatcher<HttpResponse<T>> {
private final String expected;

// Constructor to register the expected Content-Type value.
HasContentType(String contentType) {
this.expected = contentType;
}

// Helper method to extract the Content-Type value from an HttpResponse.
private String getContentType(HttpResponse<T> resp) {
return resp.headers().firstValue("Content-Type").orElse("");
}

// Performs the actual assertion.
@Override public boolean matchesSafely(HttpResponse<T> resp) {
return getContentType(resp).equals(expected);
}

// Describes the "Expected:" value in assertion failure messages.
@Override public void describeTo(Description description) {
description.appendText(expected);
}

// Describes the actual ("but:") value in assertion failure messages.
@Override public void describeMismatchSafely(
HttpResponse<T> resp, Description description) {
description.appendText(getContentType(resp));
}
}
@@ -0,0 +1,23 @@
package com.mike_bland.training.testing.matchers;

import org.hamcrest.Matcher;

import java.net.http.HttpResponse;

// Collection of custom Hamcrest Matcher<T> classes for assertThat() statements.
//
// These tutorials describe how to write Matchers:
//
// - https://hamcrest.org/JavaHamcrest/tutorial
// - https://www.baeldung.com/hamcrest-custom-matchers
//
// Note that these tutorials show the static factory functions defined on the
// same class as the Matcher. However, Hamcrest itself collects these factories
// into its own org.hamcrest.Matchers class for convenience, instead of
// importing one class per Matcher.
public class Matchers {
public static <T> Matcher<HttpResponse<T>> hasContentType(
String contentType) {
return new HasContentType<>(contentType);
}
}
Expand Up @@ -10,6 +10,7 @@
import com.mike_bland.training.testing.annotations.MediumTest;
import com.mike_bland.training.testing.utils.PortPicker;
import com.mike_bland.training.testing.utils.TestTomcat;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;

Expand All @@ -20,6 +21,7 @@
import java.net.http.HttpResponse.BodyHandlers;
import java.util.Optional;

import static com.mike_bland.training.testing.matchers.Matchers.hasContentType;
import static com.mike_bland.training.testing.stringcalculator.Servlet.DEFAULT_ROOT;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
Expand Down Expand Up @@ -65,11 +67,8 @@ void servesCorrectLandingPage() throws Exception {

var resp = sendRequest(req);

assertEquals(200, resp.statusCode());
assertEquals(
Optional.of("text/html"),
resp.headers().firstValue("Content-Type")
);
assertEquals(HttpServletResponse.SC_OK, resp.statusCode());
assertThat(resp, hasContentType("text/html"));
assertThat(
resp.body(),
containsString("<title>String Calculator - ")
Expand All @@ -86,11 +85,8 @@ void addEndpointPlaceholder() throws Exception {

var resp = sendRequest(req);

assertEquals(200, resp.statusCode());
assertEquals(
Optional.of("text/plain;charset=UTF-8"),
resp.headers().firstValue("Content-Type")
);
assertEquals(HttpServletResponse.SC_OK, resp.statusCode());
assertThat(resp, hasContentType("text/plain;charset=UTF-8"));
assertEquals("placeholder for /add API endpoint", resp.body());
}
}

0 comments on commit b987997

Please sign in to comment.