Integration Testing with Spring – Testing A REST API

Gil Zilberfeld talks about integration testing a REST API
Standard
This is a short series of how to use Spring in integration testing and unit testing.
ConfigurationsMocking

Testing a REST API
A custom configurationConfiguration logic

After we understand how to use mocks in Spring in integration tests, let’s take a look at a setup for testing a REST service that uses a dependency we want to mock. API testing is a usual integration test scenario, and with those, we might need to mock dependencies buried under the API layer. Spring to the rescue.

Our StudentService contains an endpoint like the one below, and we’d like to mock the student in integration tests for both cases (either null or not):

@Autowired
private Student student;

@RequestMapping(value="/name",method = RequestMethod.GET)
public String getStudentName() {
	String name = student.getName();
	if (name == null)
		return "unknown";
	return name;
}

We’ll use the updated configuration from last time, without any behavior setup:

@Configuration
public class MockInjectionConfiguration {
	
	@Bean
	public Student student() {
		Student mockStudent = Mockito.mock(Student.class);
		return mockStudent;
	}
}

Mockito’s default is to return null on methods we didn’t use when on.That means that when getName() is called, null is returned.

Now we need to setup the integration test class:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = StudentApplication.class, 
	webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = { MockInjectionConfiguration.class })
public class restServiceTestWithMockedStudent {

        @Autowired
	private Student mockStudent;
        
        @LocalServerPort
	private int port;
	
	TestRestTemplate restTemplate;
	HttpHeaders headers;
	HttpEntity<String> entity;
	ResponseEntity<String> response;

	@Before
	public void setup() {
		headers = new HttpHeaders();
		restTemplate = new TestRestTemplate();
		entity = new HttpEntity<String>(null, headers);
	}

	@Test
	public void whenNoStudentName_returnUnknown() throws JSONException {

		response = 
			restTemplate.exchange(
				createURLWithPort("/rest/student/name"),
				HttpMethod.GET, entity, String.class);

		assertEquals("unknown", response.getBody());
	}

	@Test
	public void whenStudentExists_returnName() throws JSONException {
                when(mockStudent.getName()).thenReturn("Gil");
		response = 
			restTemplate.exchange(
				createURLWithPort("/rest/student/name"),
				HttpMethod.GET, entity, String.class);

		assertEquals("Gil", response.getBody());
	}
	
	private String createURLWithPort(String uri) {
		return "http://localhost:" + port + uri;
	}

}

Integration tests are a bit more elaborate than regular unit tests. Let’s break it down. Let’s take a look at the annotation part first:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { MockInjectionConfiguration.class })
@SpringBootTest(classes = StudentApplication.class, 
	webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

The @RunWith and @ContextConfiguration annotations are used just like we used them before – selecting Spring as the JUnit runner, and choosing the right configuration. We’ve also let Spring Boot know which service class to run (that includes our service), and allow it to select a random port, using @SpringBootTest annotation . This port number will be injected using the @LocalServerPort annotation in the class.

In addition, we @Autowired the mockStudent. We need it for the second test (it’s not needed in the first integration test, because we’re using the default setting, returning null).

The @Before method just sets up things for the call:

@Before
public void setup() {
	headers = new HttpHeaders();
	restTemplate = new TestRestTemplate();
	entity = new HttpEntity<String>(null, headers);
}

After that, it’s using the TestRestTemplate to invoke the service. The first integration test checks the return value for a non-existent student:

@Test
public void whenNoStudentName_returnUnknown() throws JSONException {
	response = 
		restTemplate.exchange(
		createURLWithPort("/rest/student/name"),
		HttpMethod.GET, entity, String.class);

	assertEquals("unknown", response.getBody());
}

Since the mock is just initialized, it will return null as the name, which will cause to return the “unknown” value, the case we want to cover in the first our integration test.

In the second integration test we’re using, we’re also setting behavior on the mock for the getName() method:

@Test
public void whenStudentExists_returnName() throws JSONException {
        when(mockStudent.getName()).thenReturn("Gil");
	response = 
		restTemplate.exchange(
			createURLWithPort("/rest/student/name"),
			HttpMethod.GET, entity, String.class);

	assertEquals("Gil", response.getBody());
}

That’s it so far. We may continue on integration tests more later.

Leave a Reply

Your email address will not be published. Required fields are marked *