Testing a custom RepositoryRestController that uses a PersistentEntityResourceAssembler - java

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.

Related

WebApplicationContext is required - MockMVC

I'm trying to unit test a controller class, but have been stuck with the following error. I tried changing notations and following some online tutorials but it has not been working, I always get this same error.
Here's the stackTrace:
java.lang.IllegalArgumentException: WebApplicationContext is required
at org.springframework.util.Assert.notNull(Assert.java:201)
at org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder.<init>(DefaultMockMvcBuilder.java:52)
at org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup(MockMvcBuilders.java:51)
at br.com.gwcloud.smartplace.catalog.controller.test.ItemControllerTest.setUp(ItemControllerTest.java:66)
...
And this is my controller test class:
#SpringBootTest
#WebMvcTest(controllers = ItemController.class)
#ActiveProfiles("test")
#WebAppConfiguration
public class ItemControllerTest {
#MockBean
private ItemRepository ir;
#Autowired
private MockMvc mockMvc;
#Autowired
private ModelMapper modelMapper;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private ObjectMapper objectMapper;
#Before
public void setUp() {
this.mockMvc = webAppContextSetup(webApplicationContext).build();
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.webApplicationContext);
this.mockMvc = builder.build();
}
#Test
public void shouldCreateNewItem() throws Exception {
ItemDTO itemDTO = new ItemDTO();
itemDTO.setName("Leo");
itemDTO.setDescription("abc description");
itemDTO.setEnabled(true);
itemDTO.setPartNumber("leo123");
Item item = itemDTO.convertToEntity(modelMapper);
mockMvc.perform(
post("/api/item/").contentType(
MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(item))).andExpect(
status().isOk());
}
}
The error you are encountering might be solved by adding:
#RunWith(SpringRunner.class)
Just below #SpringBootTest. Or you could instead extend AbstractJUnit4SpringContextTests.
Another problem is that the WebApplicationContext might not be available in an #Before annotated method. Try moving it into the test method itself.
That said, I usually avoid unit testing controllers, since I don't put any business logic in them. The stuff controllers do is specifying paths, mapping request arguments, error handling (although that is better handled in a separate ControllerAdvice class), setting up the view model and view target etc. These are all what I call 'plumbing' and tie in heavily with the framework you are using. I don't unit test that.
Instead, this plumbing can be validated by having a couple of high level integration tests that actually do a remote call to the controller and execute a complete flow, including all the plumbing.
Any business logic should be taken outside of the controller (typically in services) and unit tested there, in isolation.
Have you tried removing #SpringBootTest and #WebAppConfiguration. If you are only interested in testing controller you don't need a full-blown application through these annotations.

Springs MockMvc returning empty content in one of two only slightly different approaches

I'm testing a Spring MVC #RestController which in turn makes a call to an external REST service. I use MockMvc to simulate the spring environment but I expect my controller to make a real call to the external service. Testing the RestController manually works fine (with Postman etc.).
I found that if I setup the test in a particular way I get a completely empty response (except the status code):
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = AnywhereController.class)
public class AnywhereControllerTest{
#Autowired
private AnywhereController ac;
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void testGetLocations() throws Exception {
...
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/anywhere/locations").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(containsString("locations")))
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON));
.andReturn();
}
The test fails because the content and headers are empty. Then I tried adding this to the test class:
#Configuration
#EnableWebMvc
public static class TestConfiguration{
#Bean
public AnywhereController anywhereController(){
return new AnywhereController();
}
}
and additionally I changed the ContextConfiguration annotation (although I'd like to know what this actually does):
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration
public class AnywhereControllerTest{...}
Now suddenly all the checks succeed and when printing the content body I'm getting all the content.
What is happening here? What is the difference between these two approaches?
Someone in the comments mentioned #EnableWebMvc and it turned out this was the right lead.
I wasn't using #EnableWebMvc and therefore
If you don't use this annotation you might not initially notice any difference but things like content-type and accept header, generally content negotiation won't work. Source
My knowledge about the inner workings of the framework is limited but it a simple warning during startup could potentially save many hours of debugging. Chances are high that when people use #Configuration and/or #RestController that they also want to use #EnableWebMvc (or the xml version of it).
Making things even worse, Spring Boot for example adds this annotation automatically, which is why many tutorials on the internet (also the official ones) don't mention #EnableWebMvc.

When running test class, properties can't be read properly from .properties file by using #Value annotation, in Spring Maven project

//Update
After viewing helpful comments, I realize the problem should then be, how to unit test method using values read from properties by #Value .
//
I am working on this issue for days, I am writing unit test for a serviceClass.The serviceClass is like below :
import ...
#Component
public class ServiceClass implements ServiceInterface {
#Value("${data.layer.url}")
private String dataLayerUrl;
#Autowired
private RestTemplate restTemplate
public void dummy(){
restTemplate.postForObject(dataLayerUrl + "/" + ... , ...);
}
}
And CONFIG_DIR is already defined in application configuration file.
I have a SomeConfig class defining beans as below. (...src/main/java/com.app/configuration/SomeConfig)
#Configuration
#ComponentScan(basePackages = {"..."})
#PropertySource(value = "file:${CONFIG_DIR}/app.properties")
public class SomeConfig{
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
...
return restTemplate;
}
}
My test class is as below:
Import ...
#Profile("test")
public class ServiceClassTest extends AbstractTest {
#Value("${data.layer.url}")
private String dataLayerUrl;
#InjectMocks
private ServiceClass ServiceClass;
#Mock
RestTemplate restTemplate;
#Before
public void initializeMockito() {
MockitoAnnotations.initMocks(this);
}
#Test
public void dummyTest(){
when(restTemplate.postForObject(dataLayerUrl + "/" + ..., ...)).thenReturn(...);
serviceClass.dummy();
assertEquals(...);
verify(restTemplate).postForObject(...);
}
}
And then my AbstractTest as below :
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
#SpringApplicationConfiguration(classes = Application.class)
#ContextConfiguration(classes = {TestConfiguration.class})
#ComponentScan(basePackages = ...)
public abstract class AbstractTest {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
}
And I also have .../src/test/resources/application-test.properties defined as below
#Datalayer properties
data.layer.url=http://camel-dev-01.xxx.com:5001
This is the same as defined in application.properties(which locates outside of project in CONFIG_DIR.
The logic of testing is just to make sure when you call dummy method of serviceClass, the postForObject method of restTemplate is called exactly once.
But when doing it this way, I am facing with 2 problems.
when I run test class in debug mode, I found
in ServiceClassTest. dataLayerUrl = "$data.layer.url"
in ServiceClass. dataLayerUrl = null
I researched around and be able to solve problem one by following this link
https://gist.github.com/danlangford/3418696
But this is not an ideal way to do this, since by default spring should be able to read properties from application-test.properties.
And I never figured out what caused the second issue and how to solve it.
I think this would be a common issue when writing unit test on class which read properties from .properties file using $Value annotation. Any comments or suggestions would be very much appreciated.
The key point hear as said M. Deinum is that you use a mix of Spring bean and Mock Object that in this case aren't Spring bean and for this reason can't benefit of the feature of Spring Container such as the injection of the properties.
In particular you should use the spring test abstraction as a "integration test" istruments. With this words I intended that you should use this abstraction, for test the correct configuration, behavior and so on fo your bean in the spring contex. However if you use Stub or mock object you actually exit, of a smal part probably, by the management of spring and the your test don't make sense. Using stub or mock the your test become a Unit test in sense that it will be a test the your bean and functionality in isolation infact you have mock or stub the dependency of your object.
I hope that this reflection could be help you
I am glad to know there is no way to read values from properties by #Value inside a mock obj.
But still my problem is that I want to unit test my dummy method in ServiceClass. Put it another way, as long as I could unit test this method, I don't care whether #Value works or not.
Here is my solution of test method
#Profile("test")
public class ServiceClassTest extends AbstractTest {
#Value("${data.layer.url}")
private String dataLayerUrl;
#InjectMocks
private ServiceClass ServiceClass;
#Mock
RestTemplate restTemplate;
#Before
public void initializeMockito() {
MockitoAnnotations.initMocks(this);
}
#Test
public void dummyTest(){
when(restTemplate.postForObject(anyString() , eq(), eq() )).thenReturn(...);
serviceClass.dummy();
assertEquals(...);
verify(restTemplate).postForObject(anyString(), eq(), eq());
}
By using anyString, I don't rely on what value is read from properties, since I only want to test whether dummy method call restTemplate's postForObject method properly.
You need to add PropertySourcesPlaceholderConfigurer to your test configuration in order to populate properties annotated with #Value annotation. Spring Boot adds it to configuration, but since your test is running without Spring Boot you have to declare it. For more details seehere .
in place of #InjectMocks you can write #Autowired and I thing you can use both annotation like that
Case 1
#InjectMocks
private ServiceClass ServiceClass;
case 2
#Autowired
#InjectMocks
private ServiceClass ServiceClass;
I have same issue but after discussion my senior I have find above like solutions

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.

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

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 :)

Categories