Does spring #GetMapping work with MockMvc - java

HelloController.java
#RestController
class HelloController {
#GetMapping(value = "{id}/hello")
public ModelAndView listAPI(#PathVariable("id") String profileId) {
ModelAndView mav = new ModelAndView();
return mav;
}
}
HelloControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = HelloConfigTest.class)
class HelloControllerTest {
#Inject
private WebApplicationContext webApplicationContext;
#Inject
private Foo mockFoo
#InjectMocks
HelloController helloController;
private MockMvc mockMvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testHello() throws Exception {
mockMvc.perform(
get("/{id}/campaigns", "id1"))
.andExpect(status().isOk()));
}
}
// I have another test that directly calls the controller method.
// So I need #InjectMocks to get an instance of the controller
#Test
public void test2() {
when(mockFoo.getX()).thenReturn(true);
helloController.saveAPI();
}
HelloConfigTest.java
#Configuration
#ComponentScan("com.test.controller")
class HelloConfigTest {
#Bean
public mockFoo() {
return Mockito.mock(Foo.class);
}
}
The response that I get here is 404 and I expect 200.
But it works and I get 200 if I change #GetMapping to #RequestMapping(value="{id}/hello", method=RequestMethod.GET)
Am I missing anything here ?

Your configuration is extremely bare bones
#Configuration
#ComponentScan("com.test.controller")
class HelloConfigTest {
It doesn't register any Spring MVC infrastructure beans, either implicitly or explicitly.
When MockMvc, internally, creates a TestDispatcherServlet to test your #Controller class, it has to defer to some default Spring MVC infrastructure types.
Among these infrastructure types is HandlerMapping which is
to be implemented by objects that define a mapping between requests and handler objects.
The default implementation used by the TestDispatcherSerlet is DefaultAnnotationHandlerMapping (an old class) which looks for #RequestMapping specifically, it doesn't recursively do a meta-annotation lookup. Your #GetMapping annotated method is therefore not found and not registered as a handler.
If, instead, you configure your application context with #EnableWebMvc
#Configuration
#ComponentScan("com.test.controller")
#EnableWebMvc
class HelloConfigTest {
Spring will implicitly register a RequestMappingHandlerMapping, which does do this "recursive" lookup for the annotation hierarchy (called merging). Since #GetMapping is annotated with #RequestMapping, the annotated handler method will be found and registered.
As for the #InjectMocks, note that the instance referenced by the field is different from the one used to handle the request performed by the MockMvc object. The former is managed by Mockito, the latter by Spring.

Related

AOP works only with #Autowired annotation

I have the following spring controller code:
#Controller
#RequestMapping("/")
public class MainController {
UserService user = new UserService();
#GetMapping("/home")
public String goFirstPage(){
user.showUserName(new User("Mike"));
return"firstpage";
}
}
and the following aspect:
#Aspect
#Component
#Order(1)
public class UserAspect {
#Before("execution(public void com.project.aopmaven.services.UserService.showUserName(..))")
public void logUser(){
System.out.println("Logging User");
}
}
It doesn't work, "Logging User" message is not shown (even the UserService object is instantiated in the controller class). But, when we add the #Autowired annotation to the UserService defined in the controller it works!
#Autowired
UserService user = new UserService();
Can anyone explain this?
Spring AOP allows using AOP on Spring beans, not on random objects. And it's based on proxies: instead of injecting the actual implementation of the bean, Spring injects a proxy that wraps the actual implementation, and invokes the aspects before/after invoking the wrapped bean implementation.
Documentation

Unit Test of file upload using MockMvcBuilders with standalone context and SpringBoot 1.2.5

I'm using Spring Boot 1.2.5-RELEASE. I have a controller that receive a MultipartFile and a String
#RestController
#RequestMapping("file-upload")
public class MyRESTController {
#Autowired
private AService aService;
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void fileUpload(
#RequestParam(value = "file", required = true) final MultipartFile file,
#RequestParam(value = "something", required = true) final String something) {
aService.doSomethingOnDBWith(file, value);
}
}
Now, the service works well. I tested it with PostMan and eveything goes as expected.
Unfortunately, I cannot write a standalone unit test for that code. The current unit test is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
public class ControllerTest{
MockMvc mockMvc;
#Mock
AService aService;
#InjectMocks
MyRESTController controller;
#Before public void setUp(){
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
//File is correctly loaded
final MockMultipartFile multipartFile = new MockMultipartFile("aMultiPartFile.txt", new FileInputStream(file));
doNothing().when(aService).doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
mockMvc.perform(
post("/file-upload")
.requestAttr("file", multipartFile.getBytes())
.requestAttr("something", ":(")
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isCreated());
}
}
Test fails with
java.lang.IllegalArgumentException: Expected MultipartHttpServletRequest: is a MultipartResolver configured?
Now, in the MultipartAutoConfiguration class from Spring Boot I see that a MultipartResolver is auto configured. But, I guess that with the standaloneSetup of MockMvcBuilders I cannot access this.
I tried several configurations of the unit test that I don't report for brevity. Especially, I also tried rest-assured as shown here, but honestly this doesn't work because it seems that I cannot mock the AService instance.
Any solution?
You are trying to combine here unit test (standaloneSetup(controller).build();) with Spring integration test (#RunWith(SpringJUnit4ClassRunner.class)).
Do one or the other.
Integration test will need to use something like code below. The problem would be faking of beans. There are ways to fake such bean with #Primary annotation and #Profile annotation (you create testing bean which will override main production bean). I have some examples of such faking of Spring beans (e.g. this bean is replaced by this bean in this test).
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Secodn option is to remove #RunWith(SpringJUnit4ClassRunner.class) and other class level configuration on your test and test controller without Spring Context with standalone setup. That way you can't test validation annotations on your controller, but you can use Spring MVC annotations. Advantage is possibility to fake beans via Mockito (e.g. via InjectMocks and Mock annotations)
I mixed what lkrnak suggested and Mockito #Spy functionality. I use REST-Assured to do the call. So, I did as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyApplication.class)
#WebAppConfiguration
#IntegrationTest({"server.port:0"})
public class ControllerTest{
{
System.setProperty("spring.profiles.active", "unit-test");
}
#Autowired
#Spy
AService aService;
#Autowired
#InjectMocks
MyRESTController controller;
#Value("${local.server.port}")
int port;
#Before public void setUp(){
RestAssured.port = port;
MockitoAnnotations.initMocks(this);
}
#Test
public void testFileUpload() throws Exception{
final File file = getFileFromResource(fileName);
doNothing().when(aService)
.doSomethingOnDBWith(any(MultipartFile.class), any(String.class));
given()
.multiPart("file", file)
.multiPart("something", ":(")
.when().post("/file-upload")
.then().(HttpStatus.CREATED.value());
}
}
the service is defined as
#Profile("unit-test")
#Primary
#Service
public class MockAService implements AService {
//empty methods implementation
}
The error says the request is not a multi-part request. In other words at that point it's expected to have been parsed. However in a MockMvc test there is no actual request. It's just mock request and response. So you'll need to use perform.fileUpload(...) in order to set up a mock file upload request.

Spring-test unexpectedly fails, how best triangulate the error?

This basic Spring test of a Spring 3 controller gives me a response code 404 result instead of the expected 200:
#RunWith(SpringJUnit4ClassRunner.class)
public class RootControllerMvcTest extends AbstractContextControllerTests {
private MockMvc mockMvc;
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(this.wac)
.alwaysExpect(status().isOk()).build();
}
#Test
public void viewIndex() throws Exception {
this.mockMvc.perform(get("/")).andExpect(view()
.name(containsString("index"))).andDo(print());
}
AbstractContextControllerTests:
#WebAppConfiguration("file:src/main/webapp/WEB-INF/spring/webmvc-config.xml")
#ContextConfiguration("file:src/main/resources/META-INF/spring/applicationContext.xml")
public class AbstractContextControllerTests {
#Autowired
protected WebApplicationContext wac; }
I have verified the controller method itself with another test, but when I use the context the test fails even as the controller does serve the proper page when run in container.
The controller in question looks like this:
#Controller
public class RootController {
#Autowired
CategoryService categoryService;
#RequestMapping(value = "/", method = RequestMethod.GET, produces = "text/html")
public String index(Model uiModel) {
uiModel.addAttribute("categories", categoryService.findAll());
return "index";
}
Clearly I'm not testing what I think. Any suggestion how to triangulate this issue?
I posted the full web mvc file at Pastebin to not clutter all space up here.
FYI: The value attribute for #WebAppConfiguration is not an XML configuration file but rather the root directory of your web application. So your current test configuration could never work.
Assuming that applicationContext.xml and webmvc-config.xml are the XML configuration files for your root and DispatcherServlet WebApplicationContexts, respectively, try redefining AbstractContextControllerTests as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy ({
#ContextConfiguration("/META-INF/spring/applicationContext.xml"),
#ContextConfiguration("file:src/main/webapp/WEB-INF/spring/webmvc-config.xml")
})
public abstract class AbstractContextControllerTests {
#Autowired
protected WebApplicationContext wac;
}
By the way, abstract test classes must actually be declared as abstract. ;)
Regards,
Sam (author of the Spring TestContext Framework)

Spring MockMvc - Custom Validators are not registered/called in Spring Container

#Configuration
#EnableWebMvc
#ComponentScan("com.xyz.web")
class BaseTestConfig extends WebMvcConfigurerAdapter {
#Bean
AccountService accountService() {
return Mockito.mock(AccountService)
}
}
class Test {
#Autowired WebApplicationContext wac
MockMvc mockMvc
#BeforeClass
void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
#Test
void testSubmitPageFails() {
mockMvc.perform(post("/createAccount")
.param("account.username", "x")
.param("confirmEmail", "y")
.param("account.firstName", "")
.param("account.lastName", "")
.param("account.zip", ""))
.andExpect(model().errorCount(5))
.andExpect(view().name("account/createAccount"))
.andExpect(status().isOk())
}
#Test
void testSubmitPageSucceeds() {
mockMvc.perform(post("/createAccount")
.param("account.username", "test#test.com")
.param("confirmEmail", "test#test.com")
.param("account.firstName", "John")
.param("account.lastName", "Doe")
.param("account.zip", "22102"))
.andExpect(flash().attributeCount(1))
.andExpect(status().isMovedTemporarily())
.andExpect(redirectedUrl("/home"))
}
}
I define my test class and config class as shown above. However, controller methods having #Valid annotations not being invoked. It seems like they are not registered during Spring container initialization.
These validators registered just fine when I run the application.
Any idea why my custom validators are ignored for my controller tests?
I enabled MockMvcResultHandlers.print() to see what the issue is. It turned out that a repository on which my validator depends was not defined. Defining that in BaseTestConfig resolved the issue.

How to send http request to mocked controller using MockMvc?

I have this class for test. This test uses mockMvc object. My opinion that this object send http requests and these requests handles controller which configuration takes from pathToFile.xml
#ContextConfiguration(locations = { "classpath:/pathToFile.xml" })
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public class CandidateControllerTest {
#Autowired
WebApplicationContext wac;
MockMvc mockMvc;
#Before
public void before() {
mockMvc = MockMvcBuilders.webApplicationContextSetup(wac).build();
}
...
}
I think that sometimes I want use controller with other configuration.
What does it mean?
CandidateControllerTest tests methods of CandidateController class
#Controller
CandidateController{
#Autowire
CandidateService candidateService;
#RequestMapping("/path")
public string handleSomething(Model model){
...
candidateService.doSomething();
...
return "viewName"
}
}
I want to mock candidateService an sent http requests to controller with mocked candidateService
It is really?
Create a setter for the candidateService in your CandidateController class.
In your CandidateControllerTest, get the CandidateController bean from the WebApplicationContext and use the setter to set the mock.
CandidateService candidateServiceMock = ...; // mock it
CandidateController cc = (CandidateController) wac.getBean(CandidateController.class);
cc.setCandidateService(candidateServiceMock);
I don't recommend this. If you were simply testing the CandidateController on its own, this would be fine. But you are testing it behind the MockMvc, which is integration testing. A mock doesn't belong in the stack being tested.

Categories