Level 7 - Milestone 11
Milestone 11 - Unit Testing the Repository Class
In this section we will complete the unit testing of the repository-layer
class
Before completion of this milestone, students will:
- Add a class/unit tests for their repository class
- Learn how to unit test code that involves WebClient
Create a Class for the Repository Test
This will be accomplished the exact same way you created a test class for the previous controller and service classes.
Unit Testing with WebClient
The one significant difference with unit testing our repository class is that we have to deal with
mocking our WebClient instance directly. Because our WebClient code exists in this repository class,
we will need to undergo a significant amount of mocking and stubbing to successfully avoid making an actual
call to the external service from which we are receiving data. As with the WebClient code itself, students should
not necessarily feel like they need to be able to recreate this code from memory. At this level of complexity, it would be
perfectly understandable to find this code, either on the internet or in a previously-completed project, and reuse it for this
class.
First thing we need to do is get access to a mocked WebClient object that we can use for our test. In our repository's constructor, you will
notice that the WebClient is not autowired, but instead we wrote some code to build it within the method. We need a way to get a the mocked WebClient
which we will create in our unit test into the instance of our repository which we will use during our testing. The easiest way to accomplish this is to simply
add another constructor to our repository class that takes a WebClient as a parameter. Then we can use that constructor for our test class to pass-in
the mock of our WebClient for use with the test.
Creating even more mocks
The WebClient code that makes the request to the external API in our repository class looks something like this:
return webClient.get() .uri(uriBuilder -> uriBuilder .queryParam("fo", "json") .queryParam("at", "results") .queryParam("q", query) .build() ).retrieve() .bodyToMono(LocResponse.class) .block() .getResults();
Unfortunately, each one of these methods that we have chained together returns a different type of object. That means that if we want to successful unit test this code, we will need to create a mock for each one of those intermediary objects. Including all of those mocks, the repository test class should look something like this:
class LocRepositoryTest { private LocRepository locRepository; @Mock WebClient webClientMock; @Mock WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock; @Mock WebClient.RequestHeadersSpec requestHeadersSpecMock; @Mock WebClient.ResponseSpec responseSpecMock; @Mock Mono<LocResponse> LocResponseMonoMock; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); locRepository = new LocRepository(webClientMock); } @Test void whenGetResults_thenReturnLocResponse() { //given //when //then } }
Creating an LocResponse in our Test Class
Next, we will need to create an instance of LocResponse within our test class. You could do this in the setUp() method and save it as a field if you need to use it in multiple tests, but since we only have a single method to test, we can also place that within the "given" section of the test method itself. We can also save the query in a variable within our method, while we may not need to reuse it in this test, often times you will find that you will be required to pass that value as a parameter to multiple method calls.
@Test void whenGetResults_thenReturnLocResponse() { String query = "Java"; LocResponse locResponse = new LocResponse(); Result result = new Result(); result.setTitle("Java: A Drink, an Island, and a Programming Language"); result.setAuthors(Collections.singletonList("AUTHOR")); result.setLink("LINK"); List<Result> expectedResults = Collections.singletonList(result); locResponse.setResults(expectedResults); //when //then }
Stubbing the WebClient Methods
This is the challenging part. Again, it is perfectly acceptable to copy and paste this section of code if you have it available somewhere. We need to stub all of the methods that are chained together in our WebClient request, so that at the end of it we are left with our expected LocResponse, instead of getting a NullPointerException somewhere along the way.
@Test void whenGetResults_thenReturnLocResponse() { //given String query = "Java"; LocResponse locResponse = new LocResponse(); Result result = new Result(); result.setTitle("Java: A Drink, an Island, and a Programming Language"); result.setAuthors(Collections.singletonList("AUTHOR")); result.setLink("LINK"); List<Result> expectedResults = Collections.singletonList(result); locResponse.setResults(expectedResults); when(webClientMock.get()) .thenReturn(requestHeadersUriSpecMock); when(requestHeadersUriSpecMock.uri((Function<UriBuilder, URI>) any())) .thenReturn(requestHeadersSpecMock); when(requestHeadersSpecMock.retrieve()) .thenReturn(responseSpecMock); when(responseSpecMock.bodyToMono(LocResponse.class)) .thenReturn(LocResponseMonoMock); when(LocResponseMonoMock.block()) .thenReturn(locResponse); //when //then }
Finishing the Test
All that is left to do is call the repository method and make the assertion, much like we have in the previous tests. Completed, our unit test should look like this:
@Test void whenGetResults_thenReturnLocResponse() { //given String query = "Java"; LocResponse locResponse = new LocResponse(); Result result = new Result(); result.setTitle("Java: A Drink, an Island, and a Programming Language"); result.setAuthors(Collections.singletonList("AUTHOR")); result.setLink("LINK"); List<Result> expectedResults = Collections.singletonList(result); locResponse.setResults(expectedResults); when(webClientMock.get()) .thenReturn(requestHeadersUriSpecMock); when(requestHeadersUriSpecMock.uri((Function<UriBuilder, URI>) any())) .thenReturn(requestHeadersSpecMock); when(requestHeadersSpecMock.retrieve()) .thenReturn(responseSpecMock); when(responseSpecMock.bodyToMono(LocResponse.class)) .thenReturn(LocResponseMonoMock); when(LocResponseMonoMock.block()) .thenReturn(locResponse); //when List<Result> actualLocResults = locRepository.getResults(query); //then assertEquals(expectedResults, actualLocResults); }You should now be able to run the test and see it pass.
Summary of Code Changes for this Milestone
-
Cheetah-Search
- src
- main
- java
- org.jointheleague.level7.cheetah
- config
- ApiDocConfig.java
- presentation
- HomeController.java
- LocController.java
- service
- LocService.java
- repository
- dto
- Result.java
- LocResponse.java
- resources
- application.yml
- test
- java
- org.jointheleague.level7.cheetah
- presentation
- HomeControllerTest.java
- LocControllerTest.java
- service
- LocServiceTest.java
- repository
- build.gradle