I have created the Home Controller below. This controller fetches the 5 dummy posts I have created in the "PostRepository" class through PostService class.
#Controller
public class HomeController {
#Autowired
PostService postService;
#RequestMapping("/")
public String getHome(Model model){
model.addAttribute("Post", postService);
return "home";
}
}
I have implemented the following test..
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebConfig.class})
#WebAppConfiguration
public class ControllerTest {
#Test //Test the Home Controller
public void TestHomePage() throws Exception{
HomeController homeController = new HomeController();
MockMvc mockMvc = standaloneSetup(homeController).build();
mockMvc.perform(get("/"))
.andExpect(view().name("home"))
.andExpect(model().attributeDoesNotExist("Post"));
}
}
The test has successfully passed. But the attribute should exist.
You are mixing two incompatible features of Spring's testing support.
If you instantiate the controller within the test, you need to use MockMvcBuilders.standaloneSetup().
If you are using the Spring TestContext Framework (i.e., #ContextConfiguration, etc.), then you need to use MockMvcBuilders.webAppContextSetup().
Thus, the following is the appropriate configuration for your test.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = WebConfig.class)
#WebAppConfiguration
public class ControllerTest {
#Autowired
WebApplicationContext wac;
#Autowired
PostService postService;
#Test
public void TestHomePage2() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
mockMvc.perform(get("/"))
.andExpect(view().name("home"))
.andExpect(model().attribute("Post",postService));
}
}
Regards,
Sam (author of the Spring TestContext Framework)
If that's the complete code, then you are missing
#RunWith(SpringJUnit4ClassRunner.class)
Related
I try to test my spring app but encounter following problem:
In "normal mode"(mvn spring-boot:run) the app starts as expected and adapterConfig gets set and is NOT NULL. When I start my testclass to test the MVC, adapterConfig does not get set. Spring ignores the whole config class.
test:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = StudentController.class)
public class StudentControllerTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentService service;
#MockBean
private StudentRepository repository;
#Test
public void shouldReturnABC() throws Exception{
MvcResult result = this.mockMvc.perform(get("/students/abc")).andReturn();
}
}
controller:
#RestController
#RequestMapping("/students")
#PermitAll
public class StudentController {
#Autowired
StudentService studentService;
//get
#GetMapping("/abc")
public String abc (){
return "abc";
}
config:
#Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private AdapterConfig adapterConfig;
#Autowired
public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
return keycloakDeployment;
}
}
adapterConfig is null when hitting the test but gets set & created when hitting it the normal way, any idea?
Using #WebMvcTest, the container will inject only components related to Spring MVC (#Controller, #ControllerAdvice, etc.) not the full configuration use #SpringBootTest with #AutoConfigureMockMvc instead.
Spring Boot Javadoc
Keycloak's AutoConfiguration is not included by #WebMvcTest.
You could
Include it manually via #Import(org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration.class)
Or use #SpringBootTest
with spring boot 2.5 i had I had to import KeycloakAutoConfiguration into my test.
#WebMvcTest(value = ApplicationController.class, properties = "spring.profiles.active:test")
#Import(KeycloakAutoConfiguration.class)
public class WebLayerTest {
// ... test code ....
}
This class is in the top of my tests hierarchy:
#TestPropertySource("/test.properties")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public abstract class ApplicationAbstractTest {
}
And few more test classes:
#WebAppConfiguration
#ActiveProfiles("mysql")
abstract public class AbstractControllerTest extends ApplicationAbstractTest {
protected MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#PostConstruct
private void postConstruct() {
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
}
JsonUserServiceTest:
#ActiveProfiles("json")
public class JsonUserServiceTest extends ApplicationAbstractTest {
#Before
public void setUp() throws Exception {
...
}
}
ContactControllerTest:
public class ContactControllerTest extends AbstractControllerTest {
#Test
public void testGet() throws Exception {
mockMvc.perform(get("/update-" + ID + "-contact")
.with(userAuth(USER)))
// .andExpect(status().isOk())
.andDo(print())
.andExpect(view().name("details"))
.andExpect(forwardedUrl("/WEB-INF/jsp/details.jsp"));
}
}
So, when I run ContactControllerTest along - it is successfull, and print method shows me:
Handler:
Type = com.telecom.web.ContactController
Method = public java.lang.String com.myApp.web.ContactController.details(java.lang.Integer,org.springframework.ui.ModelMap)
But when I run all tests, so JsonUserServiceTest runs first, ContactControllerTest fails. And print shows:
Handler:
Type = null
...
java.lang.AssertionError: No ModelAndView found
What is wrong in configuration? Or how troubleshoot it?
UPD:
at the same time, test like this, allways works fine:
public class UserControllerTest extends AbstractControllerTest {
#Test
public void testRegister() throws Exception {
mockMvc.perform(get("/register"))
.andDo(print())
.andExpect(view().name("profile"))
.andExpect(forwardedUrl("/WEB-INF/jsp/profile.jsp"));
}
}
UPD:
There is controller's method I'm testing:
#GetMapping("/update-{id}-contact")
public String details(#PathVariable Integer id, ModelMap model) {
Integer userId = AuthorizedUser.id();
LOG.info("get contact {} for User {}", id, userId);
Contact contact = service.get(id, userId);
model.addAttribute("contact", contact);
return "details";
}
I also have such bean:
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
UPD: I've tried configure mockMvc in separate class:
#Configuration
public class TestConfig {
#Autowired
private WebApplicationContext webApplicationContext;
#Bean
public MockMvc mockMvc() {
return MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.build();
}
}
And added it here:
#WebAppConfiguration
#ContextConfiguration(classes = {TestConfig.class})
#ActiveProfiles("mysql")
abstract public class AbstractControllerTest extends ApplicationAbstractTest {
but I've received:
java.lang.IllegalStateException: springSecurityFilterChain cannot be
null. Ensure a Bean with the name springSecurityFilterChain
implementing Filter is present or inject the Filter to be used.
The WARN message doesn't cause the test cases to fail. It just says that Entity manager factory is registered twice. This will only be an issue if you cluster your application using the same Entity Manager Factory. For test case run it is not a cause for concern.
The root cause of the testcase failure is in these two lines
.andExpect(view().name("details"))
.andExpect(forwardedUrl("/WEB-INF/jsp/details.jsp"));
Please check if the project has a view named "details" and the forwardded url is "/WEB-INF/jsp/details.jsp"
Update
Could you please try this
#Configuration
public class TestConfig {
#Autowired
private Filter springSecurityFilterChain;
#Autowired
private WebApplicationContext webApplicationContext;
#Bean
public MockMvc mockMvc() {
return MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurityFilterChain)
.build();
}
}
Create a configuration file that will initialize mocking objects for your test cases. And put at all test case classes.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class})
It will initialize all your mocking objects only once and cached after that and reused for all test cases.
Or if you don't want to use mocking configuration, you can directly
pass the actual application configuration to ContextConfiguration as
below
For annotation based application configuration (here AppConfig and AppConfig2 are your configuration class)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {AppConfig.class, AppConfig2.class})
For xml based application configuration (here appConfig.xml and appConfig2.xml are your configuration files)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:pathTo/appConfig.xml","classpath:pathTo/appConfig2.xml"})
Reference : JUnit + Spring integration example
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.
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)
I'm trying to unit test a Spring 4.0.0 MVC application.
My controller is defined as follow:
#Controller
#RequestMapping("/test")
public class TestCtrl {
#Autowired
private TestService testService;
#Autowired
private TestRessourceAssembler testRessourceAssembler;
#Autowired
private ResponseComposer responseComposer;
#RequestMapping(value = "", method = RequestMethod.GET,produces = "application/json")
public HttpEntity showAll(Pageable pageable) {
Page<Test> patr = testService.getAll(pageable);
return responseComposer.composePage(patr,testRessourceAssembler);
}
#RequestMapping(value = "/{name}", method = RequestMethod.GET)
public HttpEntity<TestRessource> show(#PathVariable String name) {
Test test = testService.getOne(name);
if(test == null){
return new ResponseEntity("Erreur !",HttpStatus.NOT_FOUND);
}
return responseComposer.compose(test,testRessourceAssembler);
}
}
My controller unit test is as follow:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
#WebAppConfiguration
#ContextConfiguration(classes = {ApplicationConfig.class, TestMongoConfig.class, RestConfig.class, WebMvcConfig.class})
public class TestCtrlTests{
#InjectMocks
TestCtrl testCtrl;
#Mock
TestService testService;
#Autowired
protected WebApplicationContext wac;
protected MockMvc mockMvc;
#Before
public void setup(){
MockitoAnnotations.initMocks(this);
when(testService.getOne("jexiste")).thenReturn(new com.thalesgroup.ito.c2s.mc.portail.test.domain.Test("jexiste",1990));
when(testService.getOne("plaf")).thenReturn(null);
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void simpleGetAnswer() throws Exception{
assertNotNull(mockMvc);
mockMvc.perform(get("/test")).andExpect(status().isOk());
mockMvc.perform(get("/test/jexiste")).andExpect(status().isOk());
mockMvc.perform(get("/test/plaf")).andExpect(status().isNotFound());
}
}
When I'm running the test, the "normal" TestService bean is injected and used (I can see the trace in the log), not the mock.
So I read some things on the internet and replaced
this.mockMvc = webAppContextSetup(this.wac).build();
with
this.mockMvc = standaloneSetup(TestCtrl.class).build();
But, and I knew it would happen, I've no more Spring context when doing this, so my PageableArgumentResolver and my other beans (testRessourceAssembler, responseComposer) aren't injected anymore... So they are Null and happen a NullPointerException.
My question is:
1) I'm I designing something wrong ?
2) If not, how can I inject a mock in my controller while keeping other beans from the context ?
Thanks to you !
I'm looked into your tests and this should work. Simply build your MockMvc on your controller with mocked beans. After this all mocks will be visible inside test.
A MockMvcBuilder that accepts #Controller registrations thus allowing full control over the instantiation and the initialization of controllers and their dependencies similar to plain unit tests, and also making it possible to test one controller at a time.
Don't use Spring Integration test! This is simple unit testing!
Fixed test
#RunWith(MockitoJUnitRunner.class)
public class TestCtrlTests{
#InjectMocks
TestCtrl testCtrl;
#Mock
TestService testService;
protected MockMvc mockMvc;
#Before
public void setup(){
when(testService.getOne("jexiste")).thenReturn(new com.thalesgroup.ito.c2s.mc.portail.test.domain.Test("jexiste",1990));
when(testService.getOne("plaf")).thenReturn(null);
this.mockMvc = standaloneSetup(testCtrl).build();
}
#Test
public void simpleGetAnswer() throws Exception{
assertNotNull(mockMvc);
mockMvc.perform(get("/test")).andExpect(status().isOk());
mockMvc.perform(get("/test/jexiste")).andExpect(status().isOk());
mockMvc.perform(get("/test/plaf")).andExpect(status().isNotFound());
}
}