I am running into an issue when trying to create a mockMvc get request, I receive a null result when requesting to get a JSON object. I have it that I can create a post fine, but struggling to receive data when invoking a GET endpoint in my controller.
AddressStepDefs.java
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= WebEnvironment.MOCK)
#Transactional
#AutoConfigureMockMvc
/**
* Address Step Definition class to execute Scenario(s) contained in Address.feature
* #author Lewis Jones
*
*/
public class AddressStepDefs {
#Autowired
private WebApplicationContext wac;
#Autowired
private MockMvc mockMvc;
private ResultActions result;
#Autowired
#MockBean
private AddressRepository addressRepo;
/**
* Build the Controller under test
*/
#BeforeClass
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AddressController()).build();
}
/**
* Set the mock server up
*/
#Before
public void serverSetup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
/**
* Build the WebApplicationContext
*/
#Given("The server is up and running")
public void the_server_is_up_and_running() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#When("I request to view an Address with id {int} at {string}")
public void i_request_to_view_an_Address_with_id_at(Integer id, String request) throws Exception {
/** Build a GET request using mockMvc **/
result = this.mockMvc.perform(get(request + id).contentType(MediaType.APPLICATION_JSON));
}
#Then("the response code should be OK {int} and the resulting Address object json should be:")
public void the_response_code_should_be_OK_and_the_resulting_Address_object_json_should_be(Integer responseCode, String json) throws Exception {
result.andExpect(status().is(responseCode));
result.andExpect(content().string(json));
}
The Controller endpoint and the request is fine.
There is data in the database.
It works WITHOUT the #MockBean, but then my post actually inserts data into the database. (Which isn't what I want)
I have tried to #InjectMocks, no luck.
Where am I going wrong? Do I have the correct annotations?
By mocking your bean, you'll get all its results as null.
You have 2 options,
You continue to mock but you define behaviors using when/then
You spy your bean: then it will do "the same" as normal and you can just stub the method you don't want.
Related
I am using Spring Cache.
I have a Spring controller and as part of the method responsible for that GET request, I have annotated it with #Cacheable(value = "customer", key = "#accountId"). In that method, it calls an API and does some business logic and then returns a DTO. With the cache annotation in place, I'm expecting on the first execution of this code will run normally but any subsequent calls later, it'll fetch the result from the cache.. that's correct right?
I'm writing a unit test to verify that the API was called once despite mocking multiple requests being sent to that controller. But the problem is that it keeps on calling the API multiple times and not calling the cache.
Unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebMvcTest(controllers = CustomerController.class, secure = false)
public class CustomerControllerTest {
private static final String ACCOUNT_ID = "1111";
#MockBean
private CustomerService customerService;
#MockBean
private CustomerPortAPI customerPortAPI;
#Autowired
MockMvc mockMvc;
#Before
public void setUp(){
when(customerService.getStatus(any())).thenReturn("test");
when(customerPortAPI.getAccount(any())).thenReturn(Account.builder().build());
}
#Test
public void shouldReturnCustomerDTOt() throws Exception {
when(customerService.Status(any())).thenReturn("test");
when(customerPortAPI.getAccount(ACCOUNT_ID)).thenReturn(Account.builder().accountId(ACCOUNT_ID).build());
mockMvc.perform(get("/customer/{accountId}/status", ACCOUNT_ID)
.accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("status").value(customer.NOT_REQUIRED.name()));
mockMvc.perform(get("/customer/{accountId}/status", ACCOUNT_ID)
.accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("status").value(customer.NOT_REQUIRED.name()));
verify(customerPorAPI, times(1)).getAccount(ACCOUNT_ID);
}}
Controller Method:
#Cacheable(value = "statusEligibility", key = "#customerId")
#GetMapping
public CustomerStatusDTO getCustomerStatus(#PathVariable String customerId) {
Customer customer = cusomterPort.getAccount(customerId);
Status status = service.getStatus(customer);
if (status.equals(Cons.REQUIRED)) {
/.../
} else {
/.../
}
I'm using Spring Boot 2.0.6 and Java 10. I did the following service that only hits an external rest api using RestTemplate.
#Service
#Slf4j
public class DbApiClientImpl implements DbApiClient {
private final String URL_DELIMITER = "/";
private RestTemplate restTemplate;
private String url;
public DbApiClientImpl(
RestTemplateBuilder restTemplate,
#Value("${dbapi.namespace}") String namespace,
#Value("${dbapi.url}") String uri,
#Value("${dbapi.username}") String username,
#Value("${dbapi.password}") String password) {
this.restTemplate = restTemplate.basicAuthorization(username,
password).build();
this.url = namespace.concat(uri);
}
#Override
#Async("asyncExecutor")
public Merchant fetchMerchant(String id) {
ResponseEntity<Merchant> response =
restTemplate.getForEntity(url.concat(URL_DELIMITER).concat(id),
Merchant.class);
return response.getBody();
}
}
And the following test using MockeRestServiceServer:
#RunWith(SpringRunner.class)
#RestClientTest(value = {DbApiClient.class})
public class DbApiClientTest {
private static final String TEST_NAME = "test";
private static final String TEST_NAME_BAD_REQUEST = "test-
1";
private static final String TEST_NAME_SERVER_ERROR =
"test-2";
#Autowired DbApiClient dbApiClient;
#Value("${dbapi.namespace}")
private String namespace;
#Value("${dbapi.url}")
private String dbApiUrl;
#Autowired private MockRestServiceServer mockServer;
#Autowired private ObjectMapper objectMapper;
#Test
public void test() throws
JsonProcessingException, IOException {
Merchant mockMerchantSpec = populateFakeMerchant();
String jsonResponse =
objectMapper.writeValueAsString(mockMerchantSpec);
mockServer
.expect(manyTimes(),
requestTo(dbApiUrl.concat("/").concat(TEST_NAME)))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(jsonResponse,
MediaType.APPLICATION_JSON));
assertNotNull(dbApiClient.fetchMerchant(TEST_NAME));
}
The thing is that I'm getting the following exception when I run the test "No further request expected HTTP GET http://localthost... excecuted"
So seems that the #Async is borking MockerServerService response...
Also, If I commented the #Async annotation everything works just fine and I get all test green.
Thanks in advance for your comments.
Update:
As per #M.Deinum's comment. I removed the CompletableFuture from the service but I'm still getting the same exception.
The problem is your code and not your test.
If you read the documentation (the JavaDoc) of AsyncExecutionInterceptor you will see the mention that only void or Future is supported as a return type. You are returning a plain object and that is internally treated as void.
A call to that method will always respond with null. As your test is running very quickly everything has been teared down already (or is in the process of being teared down) no more calls are expected to be made.
To fix, fix your method signature and return a Future<Merchant> so that you can block and wait for the result.
#Override
#Async("asyncExecutor")
public Future<Merchant> fetchMerchant(String id) {
ResponseEntity<Merchant> response =
restTemplate.getForEntity(url.concat(URL_DELIMITER).concat(id),
Merchant.class);
return CompletableFuture.completedFuture(response.getBody());
}
Now your calling code knows about the returned Future as well as the Spring Async code. Now in your test you can now call get on the returned value (maybe with a timeout to receive an error if something fails). TO inspect the result.
I have following user resource, method createUser is secured to ADMIN role.
#RestController
#RequestMapping("/api")
public class UserResource {
#PostMapping("/users")
#Secured(AuthoritiesConstants.ADMIN)
public ResponseEntity<User> createUser(#Valid #RequestBody UserDTO userDTO) throws URISyntaxException {
log.debug("REST request to save User : {}", userDTO);
// rest of code
}
}
And following spring boot test
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyappApp.class)
public class UserResourceIntTest {
// other dependencies
#Autowired
FilterChainProxy springSecurityFilterChain;
private MockMvc restUserMockMvc;
private User user;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
UserResource userResource = new UserResource(userRepository, userService, mailService);
this.restUserMockMvc = MockMvcBuilders.standaloneSetup(userResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setMessageConverters(jacksonMessageConverter)
.apply(SecurityMockMvcConfigurers.springSecurity(springSecurityFilterChain))
.build();
}
#Test
#Transactional
#WithMockUser(username="user", password = "user", authorities = {"ROLE_USER"})
public void createUser() throws Exception {
// Create the User
ManagedUserVM managedUserVM = new ManagedUserVM();
// set user properties
restUserMockMvc.perform(post("/api/users")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
.andExpect(status().isCreated());
}
}
I expect the test to fail because api is only allowed for ADMIN role while mock is using USER role, but test is passing.
Any help will be really appreciated.
Note: The JHipster version I'm using is 5.2.0. No guarantees that this will work for all versions.
If you are using a service (which you should), you can annotate the service method. Using #WithMockUser in the integration test should then just work without having to make any other changes. Here's an example. Note that I'm also using a service interface (pass the "serviceImpl" flag in JDL), but it will work in the service implementation as well.
/**
* Service Interface for managing Profile.
*/
public interface ProfileService {
/**
* Delete the "id" profile.
*
* #param id the id of the entity
*/
#Secured(AuthoritiesConstants.ADMIN)
void delete(Long id);
The corresponding rest controller method (auto-generated by JHipster):
/**
* DELETE /profiles/:id : delete the "id" profile.
*
* #param id the id of the profileDTO to delete
* #return the ResponseEntity with status 200 (OK)
*/
#DeleteMapping("/profiles/{id}")
#Timed
public ResponseEntity<Void> deleteProfile(#PathVariable Long id) {
log.debug("REST request to delete Profile : {}", id);
profileService.delete(id);
return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
}
JHipster uses MockMvcBuilders.standaloneSetup that get passed a controller instantiated manually (not with Spring and therefore not with AOP).
Therefore the PreAuthorize is not intercepted and security check is skipped.
You can therefore either #Autowire your controller and pass it to MockMvcBuilders.standaloneSetup which kind of defies the purpose of usesing standalone setup or simply use a WebApplicationContext: MockMvcBuilders.webAppContextSetup with and autowired WepAppContext.
Try removing #WithMockUser annotation and change the test method as below
ManagedUserVM managedUserVM = new ManagedUserVM();
managedUserVM.setLogin(DEFAULT_LOGIN);
managedUserVM.setPassword(DEFAULT_PASSWORD);
managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
For complete test. You can refer to this.
Test Class
I have a small Spring MVC project. I'm writing MockMvc tests for it. Most of them work, but this one (the first one I've tried with a plain JSON body) is giving me trouble. I keep getting a NullPointerException from deep within Spring. I tried debugging through it, but eventually ran out of attached Spring source code without getting any closer to an answer.
My JSON block is captured from a live user test, which works fine. But in the test, it throws NPE. If I modify the JSON block to be malformed (IE, add an extra comma somewhere), then it throws a 400 Bad Request, as expected. Remove the extra comma, go back to NPE. Making the block invalid (IE, making a field null which is marked #NotNull in my domain object) does not give the expected 400 Bad Request. It just stays with the NPE.
All my other tests so far have been for controllers which just use query string params, and have worked fine. Also, I have one which due to browser restrictions on our customer's side must embed its JSON in a POST param (IE, "json = "{blah:blah}"), which I pull out and manually parse. That works fine, too.
Controller:
#RestController
public class SaveController {
#Autowired
private MyDao myDao;
#RequestMapping(value = "/path/to/controller", method = RequestMethod.POST)
#PreAuthorize("hasAnyRole('myRole', 'myAdminRole')")
public void updateThing(#Valid #RequestBody MyThing myThing) throws IOException {
myDao.updateMyThing(myThing);
}
}
Base test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = {TestDataAccessConfiguration.class, TestApplicationConfiguration.class})
public abstract class AbstractSpringTestCase {
#Autowired
protected WebApplicationContext wac;
protected MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
MockitoAnnotations.initMocks(this);
}
}
Test:
public class SaveControllerTest extends AbstractSpringTestCase {
#Mock
private MyDao myDao;
#InjectMocks
#Autowired
private SaveController classUnderTest;
private static final JSON = "<a big JSON string captured from (working) production>";
#Test
public void testHappyPath() throws Exception {
mockMvc.perform(post("/path/to/controller")
.contentType(MediaType.APPLICATION_JSON)
.content(JSON))
.andExpect(status().isOk());
}
}
Stacktrace:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:170)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:137)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:145)
at SaveControllerTest.testHappyPath(SaveControllerTest.java)
In order to send a request body using Spring MockMvc you must map the #RequestBody object as a json string.
Here's an example:
SingupController:
#PostMapping("/signup")
public #ResponseBody
RealityKeeper createUser(#RequestBody SignupRequest signupRequest) {
System.out.println("SignupRequest: " + signupRequest);
String password = signupRequest.getPassword();
String username = signupRequest.getUsername();
String encoded = passwordEncoder.encode(password);
RealityKeeper realityKeeper = new RealityKeeper(username, encoded);
return repository.save(realityKeeper);
}
SignupControllerTest:
#Test
public void createUser() throws Exception {
SignupRequest signupRequest = new SignupRequest("foo", "bar");
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
String requestJson=ow.writeValueAsString(signupRequest);
mockMvc.perform(post("/api/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson))
.andExpect(MockMvcResultMatchers.status().isOk());
}
Using jackson's object mapper you can turn a pojo into a json string and pass it to the mockMvc content method.
Hope this helps
In my integration test, I tried to use resttemplate to send a Get request to a dummy server created by MockMvcBuilders. However I got an error:
I/O error on GET request for "http://localhost:8080/test":Connection refused:
(In the function testAccess(), url is "http://localhost:8080/test"). My code is as below:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest("server.port=8080")
public class MyTest {
private MockMvc mockMvc = null;
#Autowired
private WebApplicationContext context;
#Value("${server.port}")
private int port;
#Autowired
private MyController myController;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
}
#Test
public void testAccess() throws Exception{
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/test";
try{
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
}
catch(ResourceAccessException ex){
System.out.println(ex.getMessage());
}
}
#Controller
public static class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public #ResponseBody String access() {
return "OK";
}
}
}
The way I've done it is this:
First, you create a mock server from the actual RestTemplate you are using in your application
#Before
public void setup() throws Exception {
mockServer = MockRestServiceServer.createServer(myService.restTemplate);
}
Then you define how that request is going to work:
mockServer.expect(requestTo("http://localhost:8080/myrestapi"))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("{ success: true }", MediaType.APPLICATION_JSON));
And last you call the method in your application that will trigger a call to that url with that RestTemplate:
#Test
public void testThis() throws Exception {
myService.somethingThatCallsMyRestApi(parameters);
}
That will make your tests work as if there was a server up and running to process requests.
Using this in your example makes no sense, cause you would be testing that you build your test correctly and nothing else from the actual application.
The problem with this is that you cannot test dynamic responses. I mean, in my case the method I'm calling generates different data every time you call it and then sends it to the mockServer and then validates that the response matches in some very specific way. I haven't found a solution yet, but if the data you are going to send and receive is previously known, which would be in most cases, you'll have no problem using this.
Why are you defining a controller in your Test class and then trying to test it ? It doesn't feel logical to try to test something that is defined within the test it self.
Rather you would want to test a controller defined somewhere outside your tests, an actual controller that is used within your application.
Let's say MyController is defined as an actual controller then you could use the mockMvc object you created to test it.
mockMvc.perform(get('/test'))
.andExpect(status().isOk())