Encouraging Good Behavior with JUnit 5 Test Interfaces

Billy Korando Effective Automated Testing With Spring Series, Java, Spring, Technology Snapshot, Testing 2 Comments

This article is part of my blog series on automated testing promoting my new Pluralsight course Effective Automated Testing with Spring.

For the third entry in my blog series, I initially planned to cover the differences between unit and integration testing. However, Nicolas Frankel covered that area pretty well recently. So instead I will cover another valuable topic: JUnit 5.

JUnit 5, released in September of 2017, is the first major release for the popular JUnit testing framework in a little over a decade. I recently presented on JUnit 5 at Lava One Conf in Hawaii in January. If you have heard about JUnit 5, but are not yet familiar with it, you can check out my presentation here, as well as the JUnit 5 User Guides.

While researching for my presentation, one new feature in JUnit 5 really caught my eye was the ability to declare tests on default methods in interfaces. This feature caught my eye because two issues I frequently face are encouraging developers to write automated tests and promoting consistent patterns across the enterprise. In this article we are going to look at how test interfaces can help accomplish both of these goals.

Properly RESTful APIs

REST is a very popular way of communicating over HTTP. Or, really, I should say that a lot of organizations say they are using REST when really their “RESTful” APIs exist little above the Swamp of POX.

Frequent offenses I have seen include returning a 200 status code despite errors having occurred, verbs in the URI (e.g.: resource/addNewResource), and incorrect HTTP method usage (e.g.: POST when GET should had be used or the inverse). I could continue, but REST, despite being a well defined specification, is very rarely followed.

So the scenario we are going to run through is demonstrating how test interfaces can be used to both test a RESTful API and encourage that API to follow the actual REST specification. A minor note for REST hardliners, I will actually be going only to Level 2 on the Richardson Maturity Scale. No hypermedia controls in this demo.

GETting Started

When writing a new REST API, I often start by implementing the GET endpoints. No particular reason why, it’s just my personal approach. However, before I begin writing production code, I need to first write my tests, or rather test interfaces.

The first interface I am writing is a parent interface that my other test interfaces will extend off of. More on them later.

public interface EndpointTest {
	String baseEndpoint();
	
	MockMvc getMockMvc();
}

This interface only has two methods: the base endpoint my controller will reside at, and a reference to the MockMvc I will use to test the Controller. This demo is being implemented in my Spring Boot demonstrator project.

Extending off the above interface, I will create another interface that will exercise the GET endpoints of; returning all resources, returning a single resource, and then a negative test of trying to retrieve a non-existent resource.

public interface GetResourceEndpointTest<T, I> extends EndpointTest {

	I getExistingResource();

	I getNonExistingResoruce();

	T foundResource();

	String getFoundResourceJsonContent();

	OngoingStubbing<T> mockExistingBehavior();

	OngoingStubbing<List<T>> mockFindAllResourcesBehavior();

	OngoingStubbing<T> mockNonExistingBehavior();

	@Test
	default void testExistingResource() throws Exception {
		mockExistingBehavior().thenReturn(foundResource());
		getMockMvc().perform(get(baseEndpoint() + "/" + getExistingResource())).andExpect(status().isOk())
				.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
				.andExpect(content().json(getFoundResourceJsonContent()));
	}

	@Test
	default void testGetAllResources() throws Exception {
		mockFindAllResourcesBehavior().thenReturn(Arrays.asList(foundResource()));
		getMockMvc().perform(get(baseEndpoint())).andExpect(status().isOk())
				.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
				.andExpect(content().json("[" + getFoundResourceJsonContent() + "]"));
	}

	@Test
	default void testNonExistingResource() throws Exception {
		mockNonExistingBehavior().thenReturn(null);
		getMockMvc().perform(get(baseEndpoint() + "/" + getNonExistingResoruce())).andExpect(status().isNotFound());
	}
}

In the above interface, I’m trying to enforce several standards.

1.) The root endpoint returns a list of all the resources.

2.) Adding an identifier to the path of the URI will return only a single instance of the resource.

3.) Giving the identifier of a non-existent resource will return a 404. This is all pretty simple REST stuff, but I’ve seen far more APIs not following this than are.

@SpringJUnitWebConfig(HotelApplication.class)
@WebMvcTest(controllers = CustomerController.class, secure = false)
public class TestCustomersController implements GetResourceEndpointTest<Customer, Long> {

	@Autowired
	private MockMvc mockMvc;

	@MockBean
	private CustomerService service;

	@Override
	public String baseEndpoint() {
		return "/customers";
	}

	@Override
	public MockMvc getMockMvc() {
		return mockMvc;
	}

	@Override
	public Long getExistingResource() {
		return 1L;
	}

	@Override
	public Long getNonExistingResoruce() {
		return 0L;
	}

	@Override
	public Customer foundResource() {
		return new Customer(1L, "Joe", "Blow", "Kokomo", "Jr.", new Date(0L));
	}

	@Override
	public String getFoundResourceJsonContent() {
		return "{\"firstName\": \"Joe\", \"lastName\" : \"Blow\", \"middleName\" : \"Kokomo\", \"suffix\" : \"Jr.\",\"dateOfLastStay\" : \"1970-01-01T00:00:00.000+0000\" }";
	}

	@Override
	public OngoingStubbing<Customer> mockExistingBehavior() {
		return when(service.findCustomerById(getExistingResource()));
	}

	@Override
	public OngoingStubbing<Customer> mockNonExistingBehavior() {
		return when(service.findCustomerById(getNonExistingResoruce()));
	}

	@Override
	public OngoingStubbing<List<Customer>> mockFindAllResourcesBehavior() {
		return when(service.findAllCustomers());
	}

}

While there are lot of methods, the actual implementation work involved in each method is only a single line. So, there isn’t too much overhead involved in implementing the interface.

After doing the work on implementing my controller class, executing the test class returns three passing tests, the tests I added as default methods in GetResourceEndpointTest.

Mixing and Matching with Interfaces

The typical REST API is going to do more than the simple retrieval of all resources or a single resource by ID. Many REST APIs will allow for the adding of new resources, updating resources, the ability to query resources, and maybe even deleting resources. However, not every REST API is going to necessarily implement all those behaviors.

This is where being able to use interfaces is nice. While Java disallows multiple inheritance, a class can implement as many interfaces as your heart desires. If your REST API is pure read, you can simply implement the GET and Search interfaces:

public class TestCustomersController implements GetResourceEndpointTest<Customer, Long>, SearchEndpointTest

If you don’t want to allow querying of resources but do need some create and delete functionality, you can just implement the GET, POST/PUT, and DELETE interfaces:

public class TestCustomersController implements GetResourceEndpointTest<Customer, Long>, PutPostEndpointTesting, DeleteResourceEndpointTest<Long>

The flexibility of being able to implement multiple interfaces is a key advantage over the simple inheritance that previous versions of JUnit offered. Rather it is implementing a REST API, a DAO, or some other pattern your organization uses, implementations typically implement only a subset the full feature set a pattern offers.

Finding the reasonable divisions in how patterns will be implemented and creating test interfaces for each of those divisions means developers using those test interfaces won’t be forced into implementing functionality just to make tests pass.

Conclusion

Getting developers to write automated tests and follow recommended patterns will always be a struggle at every organization. However new features in JUnit 5 (like the test interfaces we just demoed in this article) will hopefully make those tasks easier.

My demo, admittedly, is a bit on the raw side. A few issues I ran into while working through the demo was difficulty remembering which interface the methods in my concrete test class came from and a little bit of method overload. I counted 22 methods in a class implementing all my test interfaces. You can view all the code for my demo here.

A few iterations and some collaboration with other developers would probably iron those issues out. I can say working on my demo definitely increased, not dampened, my enthusiasm of test interfaces. I hope this article piqued your interest into using test interfaces at your organization as well.

Researching and using JUnit 5 has made me realize just how much was missing in JUnit 4. JUnit 4 was great in 2006, but a lot has changed since then. Not the least of which: the expectations and needs out of our testing frameworks. I plan on returning to JUnit 5 again in the future, but hopefully this article demonstrated some of the new possibilities JUnit 5 brings to the table.

Automated Testing Series

  1. Without Automated Testing You Are Building Legacy
  2. Four Common Mistakes That Make Automated Testing More Difficult
  3. This Post –> Encouraging Good Behavior with JUnit 5 Test Interfaces
  4. Conditionally Disabling and Filtering Tests in JUnit 5
  5. What’s New in JUnit 5.1
  6. Fluent Assertions with AssertJ
  7. Why Am I Writing This Test?
  8. What’s New in JUnit 5.2

Receive New Blogs By Email

Subscribe to this dev blog & receive new posts by email.


About the Author
Billy Korando

Billy Korando

Twitter

Billy Korando is a software consultant with Keyhole Software in Kansas City and currently working on-site with USDA. Billy has over a decade of experience in Java web development and has worked in the exciting industries like insurance, shipping, healthcare, finance, and government.


Share this Post

Comments 2

  1. This is the first article I’ve found where someone makes use of interface default methods. I’ve been waiting for this ever since i tried interface contract tests (à la Rainsberger), and always ended up having to use superclasses because Junit was not supporting @Test declared in interface method with a default implementation. Can you recommend other learning resources on ‘modern’ effective testing (does not have to be java)?

    1. Billy Korando Post
      Author

      Hey Stephane,

      I want to apologize for being so late in replying to you. I thought I was monitoring my blogs closely for questions, but this went under my radar. I’m glad you enjoyed my article and got to see a real use case the default interface method.

      For other resources on testing, well I do have my new Pluralsight course: https://www.pluralsight.com/courses/effective-testing-with-spring

      🙂

      https://www.petrikainulainen.net/ is a really good source and I would highly recommend his newsletter as he combines a lot of recent articles being posted around the internet on.

      I do need to get better about answering this question, because I get asked it a lot and I don’t always feel my answers aren’t very good. Mostly because I have learned my testing habits in a hodgepodge kind of way, so I can’t really point to a single book/source that does a really good job of introducing automated testing best practices. Hope this helps, let me know if you have any more questions.

Leave a Reply