How to mock remote REST API in unit test with Spring? - java

Assume I have made a simple client in my application that uses a remote web service that is exposing a RESTful API at some URI /foo/bar/{baz}. Now I wish to unit test my client that makes calls to this web service.
Ideally, in my tests, I’d like to mock the responses I get from the web service, given a specific request like /foo/bar/123 or /foo/bar/42. My client assumes the API is actually running somewhere, so I need a local "web service" to start running on http://localhost:9090/foo/bar for my tests.
I want my unit tests to be self-contained, similar to testing Spring controllers with the Spring MVC Test framework.
Some pseudo-code for a simple client, fetching numbers from the remote API:
// Initialization logic involving setting up mocking of remote API at
// http://localhost:9090/foo/bar
#Autowired
NumberClient numberClient // calls the API at http://localhost:9090/foo/bar
#Test
public void getNumber42() {
onRequest(mockAPI.get("/foo/bar/42")).thenRespond("{ \"number\" : 42 }");
assertEquals(42, numberClient.getNumber(42));
}
// ..
What are my alternatives using Spring?

Best method is to use WireMock.
Add the following dependencies:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-core</artifactId>
<version>4.0.6</version>
</dependency>
Define and use the wiremock as shown below
#Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
String response ="Hello world";
StubMapping responseValid = stubFor(get(urlEqualTo(url)).withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse().withStatus(200)
.withHeader("Content-Type", "application/json").withBody(response)));

If you use Spring RestTemplate you can use MockRestServiceServer. An example can be found here REST Client Testing With MockRestServiceServer.

If you want to unit test your client, then you'd mock out the services that are making the REST API calls, i.e. with mockito - I assume you do have a service that is making those API calls for you, right?
If on the other hand you want to "mock out" the rest APIs in that there is some sort of server giving you responses, which would be more in line of integration testing, you could try one of the many framework out there like restito, rest-driver or betamax.

You can easily use Mockito to mock a REST API in Spring Boot.
Put a stubbed controller in your test tree:
#RestController
public class OtherApiHooks {
#PostMapping("/v1/something/{myUUID}")
public ResponseEntity<Void> handlePost(#PathVariable("myUUID") UUID myUUID ) {
assert (false); // this function is meant to be mocked, not called
return new ResponseEntity<Void>(HttpStatus.NOT_IMPLEMENTED);
}
}
Your client will need to call the API on localhost when running tests. This could be configured in src/test/resources/application.properties. If the test is using RANDOM_PORT, your client under test will need to find that value. This is a bit tricky, but the issue is addressed here: Spring Boot - How to get the running port
Configure your test class to use a WebEnvironment (a running server) and now your test can use Mockito in the standard way, returning ResponseEntity objects as needed:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestsWithMockedRestDependencies {
#MockBean private OtherApiHooks otherApiHooks;
#Test public void test1() {
Mockito.doReturn(new ResponseEntity<Void>(HttpStatus.ACCEPTED))
.when(otherApiHooks).handlePost(any());
clientFunctionUnderTest(UUID.randomUUID()); // calls REST API internally
Mockito.verify(otherApiHooks).handlePost(eq(id));
}
}
You can also use this for end-to-end testing of your entire microservice in an environment with the mock created above. One way to do this is to inject TestRestTemplate into your test class, and use that to call your REST API in place of clientFunctionUnderTest from the example.
#Autowired private TestRestTemplate restTemplate;
#LocalServerPort private int localPort; // you're gonna need this too
How this works
Because OtherApiHooks is a #RestController in the test tree, Spring Boot will automatically establish the specified REST service when running the SpringBootTest.WebEnvironment.
Mockito is used here to mock the controller class -- not the service as a whole. Therefore, there will be some server-side processing managed by Spring Boot before the mock is hit. This may include such things as deserializing (and validating) the path UUID shown in the example.
From what I can tell, this approach is robust for parallel test runs with IntelliJ and Maven.

What you are looking for is the support for Client-side REST Tests in the Spring MVC Test Framework.
Assuming your NumberClient uses Spring's RestTemplate, this aforementioned support is the way to go!
Hope this helps,
Sam

Here is a basic example on how to mock a Controller class with Mockito:
The Controller class:
#RestController
#RequestMapping("/users")
public class UsersController {
#Autowired
private UserService userService;
public Page<UserCollectionItemDto> getUsers(Pageable pageable) {
Page<UserProfile> page = userService.getAllUsers(pageable);
List<UserCollectionItemDto> items = mapper.asUserCollectionItems(page.getContent());
return new PageImpl<UserCollectionItemDto>(items, pageable, page.getTotalElements());
}
}
Configure the beans:
#Configuration
public class UserConfig {
#Bean
public UsersController usersController() {
return new UsersController();
}
#Bean
public UserService userService() {
return Mockito.mock(UserService.class);
}
}
The UserCollectionItemDto is a simple POJO and it represents what the API consumer sends to the server. The UserProfile is the main object used in the service layer (by the UserService class). This behaviour also implements the DTO pattern.
Finally, mockup the expected behaviour:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
#Import(UserConfig.class)
public class UsersControllerTest {
#Autowired
private UsersController usersController;
#Autowired
private UserService userService;
#Test
public void getAllUsers() {
initGetAllUsersRules();
PageRequest pageable = new PageRequest(0, 10);
Page<UserDto> page = usersController.getUsers(pageable);
assertTrue(page.getNumberOfElements() == 1);
}
private void initGetAllUsersRules() {
Page<UserProfile> page = initPage();
when(userService.getAllUsers(any(Pageable.class))).thenReturn(page);
}
private Page<UserProfile> initPage() {
PageRequest pageRequest = new PageRequest(0, 10);
PageImpl<UserProfile> page = new PageImpl<>(getUsersList(), pageRequest, 1);
return page;
}
private List<UserProfile> getUsersList() {
UserProfile userProfile = new UserProfile();
List<UserProfile> userProfiles = new ArrayList<>();
userProfiles.add(userProfile);
return userProfiles;
}
}
The idea is to use the pure Controller bean and mockup its members. In this example, we mocked the UserService.getUsers() object to contain a user and then validated whether the Controller would return the right number of users.
With the same logic you can test the Service and other levels of your application. This example uses the Controller-Service-Repository Pattern as well :)

Related

Is it possible to only load spring security config to my test with junit 5?

I want to unit test my controllers security using the method based security when I've checked the docs I've found this but it uses #RunWith and it doesn't seem to be loading only the security config.
What I tried to do:
Loading my secuirty config
#ContextConfiguration(classes = {SecurityConfigImpl.class})
public class PeopleGQLApiSecutiryTest {
private final PersonService personService = mock(PersonService.class);
private final PeopleGQLApi peopleGQLApi = new PeopleGQLApi(personService);
#Test
#WithAnonymousUser
void itShouldCreateNewPerson() {
when(personService.createNewPerson(any(Person.class))).thenReturn(new Person());
// this should fail!
peopleGQLApi.createPerson(
Person.LevelEnum.WEAK,
// rest of the arguments
);
}
}
This one should fail (by throwing an expection or anything like that) because create person is a secured function with ROLE_ADMIN.
Note: this is a GraphQL mutation
#MutationMapping("createPerson")
#Secured({"ROLE_ADMIN"})
public Person createPerson(
#Argument("level") Level level,
// rest of the arguments
) {
// method implementation
}
I can't use a fully fledged #SpringBootTest because I will have to provide mongodb config too which out of the scope of this test.
I think the word you are looking for is #WithMockUser
You can use something like #WithMockUser(authorities = "ROLE_ADMIN") to mock a user with this role
Also for more tweaks, you can use the Spring Security Context
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("username", "password"));
SecurityContextHolder.setContext(securityContext);
And you can add any authorities to your security context or read them.
better to add them to securityUtil class to be used in multiple situations as well.
Please make sure that you have an assertion to validate the unauthorized requests inside your test case

Is it possible to test Spring REST Controllers without #DirtiesContext?

I'm working on a Spring-Boot web application. The usual way of writing integration tests is:
#Test
#Transactional
#Rollback(true)
public void myTest() {
// ...
}
This works nicely as long as only one thread does the work. #Rollback cannot work if there are multiple threads.
However, when testing a #RestController class using the Spring REST templates, there are always multiple threads (by design):
the test thread which acts as the client and runs the REST template
the server thread which receives and processes the request
So you can't use #Rollback in a REST test. The question is: what do you use instead to make tests repeatable and have them play nicely in a test suite?
#DirtiesContext works, but is a bad option because restarting the Spring application context after each REST test method makes the suite really slow to execute; each test takes a couple of milliseconds to run, but restarting the context takes several seconds.
First of all, testing a controller using a Spring context is no unit test. You should consider writing a unit test for the controller by using mocks for the dependencies and creating a standalone mock MVC:
public class MyControllerTest {
#InjectMocks
private MyController tested;
// add #Mock annotated members for all dependencies used by the controller here
private MockMvc mvc;
// add your tests here using mvc.perform()
#Test
public void getHealthReturnsStatusAsJson() throws Exception {
mvc.perform(get("/health"))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status", is("OK")));
}
#Before
public void createControllerWithMocks() {
MockitoAnnotations.initMocks(this);
MockMvcBuilders.standaloneSetup(controller).build()
}
}
This even works if you use an external #ControllerAdvice for error handling etc by simply calling setControllerAdvice() on the MVC builder.
Such a test has no problems running in parallel and is much faster by no need to setup a Spring context at all.
The partial integration test you described is also useful to make sure the right wiring is used and all tested units work together as expected. But I would more go for a more general integration test including multiple/all endpoints checking if they work in general (not checking the edge cases) and mocking only services reaching out to external (like internal REST clients, replacing the database by one in memory, ...). With this setup you start with a fresh database and maybe will not even need to rollback any transaction. This is of course most comfortable using a database migration framework like Liquibase which would setup your in memory db on the fly.
Below is my implement followed by #4
private MockMvc mockMvc;
#Mock
private LoginService loginService;
#Mock
private PersonalService personalService;
#InjectMocks
private LoginController loginController;
#BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(loginController).build();
}
#Test
void simple_login() throws Exception {
Mockito.when(loginService.login(Mockito.anyString(), Mockito.anyString()))
.thenReturn(UserAccessData.builder()
.accessToken("access_token_content")
.refreshToken("refresh_token_count")
.build());
mockMvc.perform(
MockMvcRequestBuilders.post("/login/simple")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.param("accountName", "1234561")
.param("password", "e10adc3949ba59abbe56e057f20f883e")
)
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(jsonPath("$.tokens.accessToken", is("access_token_content")))
.andExpect(jsonPath("$.tokens.refreshToken", is("refresh_token_count")))
;
}

Testing a custom RepositoryRestController that uses a PersistentEntityResourceAssembler

I have a RepositoryRestController that exposes resources for some persistent entities.
I have a method on my controller that takes a PersistentEntityResourceAssembler to help me generate the resources automatically.
#RepositoryRestController
#ExposesResourceFor(Customer.class)
#RequestMapping("/api/customers")
public class CustomerController {
#Autowired
private CustomerService service;
#RequestMapping(method = GET, value="current")
public ResponseEntity getCurrent(Principal principal Long id, PersistentEntityResourceAssembler assembler) {
return ResponseEntity.ok(assembler.toResource(service.getForPrincipal(principal)));
}
}
(Contrived example, but it saves going into too much detail about irrelevant details of my use-case)
I'd like to write a test for my controller (my real use-case is actually worth testing), and am planning on making use of #WebMvcTest.
So I have the following test class:
#RunWith(SpringRunner.class)
#WebMvcTest(CustomerController.class)
#AutoConfigureMockMvc(secure=false)
public class CustomerControllerTest {
#Autowired
private MockMvc client;
#MockBean
private CustomerService service;
#Test
public void testSomething() {
// test stuff in here
}
#Configuration
#Import(CustomerController.class)
static class Config {
}
}
But I get an exception saying java.lang.NoSuchMethodException: org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.<init>()
Presumably something is not being configured correctly here because I'm missing the entire data layer. Is there some way of mocking out the PersistentEntityResourceAssembler? Or another approach I could use here?
I ended up for now with:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
The downsite of it is that the test would start the full Spring application context (but without the server).
I ended up doing a slightly hacky solution here:
I removed PersistentEntityResourceAssembler from the controller method.
I added an #Autowired RepositoryEntityLinks to the controller, on which I call linkToSingleResource to create the links as needed.
I added an #MockBean RepositoryEntityLinks to my test class, and configured the mocking to return something sensible:
given(repositoryEntityLinks.linkToSingleResource(any(Identifiable.class)))
.willAnswer(invocation -> {
final Identifiable identifiable = (Identifiable) invocation.getArguments()[0];
return new Link("/data/entity/" + identifiable.getId().toString());
});
It's far from ideal - I'd love to know if there's a way of getting just enough of the data layer up that I can depend on PersistentEntityResourceAssembler.

How to test output HTTP request content in Spring application?

I have the Spring application.
We have the service which makes HTTP requests to external services. Now I think about writing Unit Test on this functionality. I want to write the integration tests. Thus I want to know that service request is correct.
Is there ways to do it in spring? (actually I don't know ways to do it outside the Spring too)
In tests you can use some kind of mock server. Wiremock for example.
Wiremock can work as a proxy and log all your requests and responses. More than that - it can then save responses and use them as stubs. It can be very useful to test integration problems with number of fault simulations.
For spring boot there is a starter:
<dependency>
<groupId>com.epages</groupId>
<artifactId>wiremock-spring-boot-starter</artifactId>
<version>0.7.18</version>
<scope>test</scope>
</dependency>
More documentation available here: https://github.com/ePages-de/restdocs-wiremock
Then create a test and setup a proxy:
#RunWith(SpringRunner.class)
#EnableAutoConfiguration
#SpringBootTest(
classes = {
YourApplication.class,
}
)
#WireMockTest // it comes from starter
public class YourTest {
#Inject
private WireMockServer server;
#Value("http://localhost:${wiremock.port}")
private String uri;
#Test
public void shouldLogAllTheThings() throws Exception {
server.stubFor(get(urlMatching("/other/service/.*"))
.willReturn(
aResponse()
.proxiedFrom("http://otherhost.com/approot")
));
// your call with rest template using uri field value
// as base uri
}
#TestConfiguration
public static class Internal {
#Inject
public WireMockConfiguration configuration;
#PostConstruct
public void wiremock() {
// this adds verbosive logger
// which will print all communication
configuration.notifier(new Slf4jNotifier(true));
}
}
}
More info:
http://wiremock.org/docs/proxying/
Mappings can be saved using this doc: http://wiremock.org/docs/record-playback/
If you don't want to use starter, you can use wiremock as JUnit rule:
http://wiremock.org/docs/junit-rule/

Spring MVC Controller testing, and mocking many classes

We have many Controllers in our system, and many Spring Data repositories.
I would like to write tests for my controllers that run through my MVC context.
However, it seems pretty cumbersome, and just not right, to have to, by hand, mock every service and repository in my system, so that I can test the controllers
e.g.
FooControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy(value = {
#ContextConfiguration(classes = { MockServices.class }),
#ContextConfiguration({ "classpath:/META-INF/spring/mvc-servlet-context.xml" }),
})
public class FooControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mvc;
#Autowired
private FooRepository fooRepository;
#Autowired
private FooService fooService;
#Before
public void setUp() throws Exception {
mvc = webAppContextSetup(wac).build();
}
#Test
public final void list() {
when(fooRepository.findAll()).thenReturn(...);
mvc.perform(get("/foo"))...
}
#Test
public final void create() {
Foo fixture = ...
when(fooService.create(fixture)).thenReturn(...);
mvc.perform(post("/foo"))...
}
}
MockServices.java
#Configuration
public class MockServices {
#Bean
public FooRespository fooRepositiory() {
return Mockito.mock(FooRespository.class);
}
#Bean
public FooService fooService() {
return Mockito.mock(FooService.class);
}
//even though we are "only" testing FooController, we still need to mock BarController's dependencies, because BarController is loaded by the web app context.
#Bean
public BarService barService() {
return Mockito.mock(FooService.class);
}
//many more "mocks"
}
I do not really want to use standaloneSetup() (want to use the production configuration, eg conversion services, error handlers, etc)
is this just the price I have to pay for writing controller tests so far down the line?
Seems there should be something like mock every class annotated with #Service or mock every interface that extends JpaRepository
An MVC Controller is implemented normally like a glue code that integrates the Model with the View. For example, when invoking an EJB from the Controller and then updating the View model.
So, a Controller test may have sence when indeed you mock all your dependencies and verify that this integration or "glue code" is working as expected. In general, if an integration test implies too many components, maybe a modularization of the whole sut may be necessary for the system to be actually testable.
Anyway, if you find integration test laborious, maybe you can try to get the most coverage for each standalone component and let functional tests get the Controller coverage.

Categories