I never did a test and decided I would do my first.
I am using Spring Boot and by default the layout is as follows: https://zapodaj.net/c8b65f1644e95.png.html
The file that is created on startup looks like this
#RunWith(SpringRunner.class)
#SpringBootTest
public class RestWebServicesApplicationTests {
#Test
public void contextLoads() {
}
}
I do not change anything here, because I do not need it.
By contrast, I created a new class
public class CheckUserDataRestControllerTest {
#Mock
private UserService userService;
#InjectMocks
private CheckUserDataRestController checkUserDataRestController;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(checkUserDataRestController).build();
}
#Test
public void textExistsUsername() throws Exception {
mockMvc
.perform(get("/checkUsernameAtRegistering")
.param("username", "jonki97"))
.andExpect(status().isOk());
}
}
To test the controller method, which should return the status OK.
#GetMapping("/checkUsernameAtRegistering")
public HttpEntity<Boolean> checkUsernameAtRegistering(#RequestParam String username) {
return ResponseEntity.ok().body(!userService.existsByUsername(username));
}
However, during the test, he throws me out
java.lang.AssertionError: Status
Expected :200
Actual :404
<Click to see difference>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
...
This is the tested controller: https://pastebin.com/MWW9YwiH
I did not make any special configurations because I did not find out that I needed to do something.
The error 404 means the Mock does not recognize your endpoint to test. The end point should be:
#Test
public void textExistsUsername() throws Exception {
mockMvc
.perform(get("/checkUserData/checkUsernameAtRegistering")
.param("username", "jonki97"))
.andExpect(status().isOk());
}
Hope this help.
Related
I have written unit test cases for a spring rest controller but I'm getting blank response body.
Below is my controller class
#RestController
#RequestMapping("/v2")
public class controller {
#Autowired
Service service;
#RequestMapping(value="/health",method=RequestMethod.POST)
public String postRequest(#RequestHeader #NotNull#NotBlank String
transID) {
return service.getTest(transID); }
}
Below is my service class
#Component
public class Service {
public String getTest(#NotNull String transid) {
return "Hello World Test";
} }
Below is my Unit Test class.I have written the unit test case for this method using mockito
class UnitTest {
#InjectMocks
controller controllerUse;
#Mock
Service service;
#Autowired
MockMvc mockMvc;
#BeforeEach
public void test() {
MockitoAnnotations.initMocks(this);
mockMvc=MockMvcBuilders.standaloneSetup(controllerUse).build();
}
#Test
public void confirmTest() throws Exception {
mockMvc.perform(post("/v2/health")
.header("transID","ABC"))
.andExpect(status().isOk())
.andDo(print())
.andReturn(); }
}
The OutPut:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I am getting response code as 200. but getting blank body.what am i missing ? is this the standard way of writing unit test cases for spring rest controller ?
Since you are injecting mock bean service, you need to mock method with your expected response.
Here is sample code using Junit 5 and Spring Boot Test artifact but you can achieve same using standard spring test module as well.
#WebMvcTest(SampleController.class)
class SampleControllerTest {
#MockBean
Service service;
#Autowired
private MockMvc mockMvc;
#Test
public void confirmTest() throws Exception {
when(service.getTest("ABC")).thenReturn("Hello World");
mockMvc.perform(post("/v2/health")
.header("transID", "ABC"))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
}
}
I write a Spring Boot app and I was able to access and test Controller with MockMvc. The issue is that during testing security is not enforced and I can access Controller with no user.
Am I doing anything wrong? Is it intended behavior?
ControllerTest class:
#RunWith(MockitoJUnitRunner.class)
public class ControllerTest {
private MockMvc mockMvc;
#Mock
private Service service;
#InjectMocks
private Controller controller;
private final static String URL = "/test";
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void test() throws Exception {
mockMvc.perform(get(URL))
.andExpect(status().isOk());
}
}
My SecurityConfig StackOverflow QA.
Your examples uses a plain unit test to test your controller. In this setup the Controller is created by Mockito (the controller field is annotated with Mockito's #InjectMocks).
Mockito is not aware of Spring, in consequence no Spring Security will be setup in your test.
You need to use the SpringRunner to run your test. This runner is Spring aware and allows you to properly initialize your controller before the test is run.
The test should look something like this (junit5):
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = Controller.class)
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service serviceMock;
#Test
public void test() throws Exception {
mockMvc.perform(get(URL))
.andExpect(status().isOk());
}
}
check our the Spring documentation or some tutorials for further information
https://spring.io/guides/gs/testing-web/
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html
I try to test a #RestController within a integration test suite using MockMvc.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class WebControllerIT {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
The #RestController (WebController) calls an injected #Service (RestClientService) which uses RestTemplate to call another REST server. This leads to the following error when running the test.
org.springframework.web.client.ResourceAccessException: I/O error on
GET request for "http://test123.com/42/status": test123.com; nested
exception is java.net.UnknownHostException: test123.com
I used MockRestServiceServer for the integration test of the #Service itself but have no idea how to archieve this within the test of #RestController.
How can I simulate a correct REST call of the RestTemplate?
The #RestController class.
#RestController
public class WebController {
private final RestClientService service;
#Autowired
public WebController(RestClientService service) {this.service = service;}
#GetMapping("/status")
public String getStatus() {
// extract pid from database ...
int pid = 42;
return this.service.getStatus(42);
}
}
The #Serviceclass.
#Service
public class RestClientService {
private final RestTemplate restTemplate;
public RestClientService(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
}
Integration/Unit testing doesn't work that way.Objective of this kind of testing is to run through your code and make sure all the business requirement are met but not to hit other system or DB.Here in your case u shouldn't be hitting test123.com to get back data.What needs to done here is that you should mock that method.
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
So that control doesn't enter this method but return you back the mock data(Dummy data).
For example let say that there are 2 kind of status this method is returning and you need to do some business validation based on the string returned.In this case u need to write 2 integration test and make sure the mocking method returns 2 different value(Dummy value instead of hitting that end point)
Reason why we are writing unit testing/integration testing is to make sure your entire code is working as expected but not to hit other system from ur code.
If you want to only test your controller layer, you would do like this.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MockServletContext.class)
#WebAppConfiguration
public class WebControllerIT {
private MockMvc mockMvc;
private RestClientService service
#Mock
private RestTemplate restTemplate
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
service = new RestClientService(restTemplate);
WebController webController = new WebController(service);
mvc = MockMvcBuilders.standaloneSetup(webController).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
//Mock the behaviour of restTemplate.
doReturn("someString").when(restTemplate).getForObject(anyString(), anyString());
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
The Controller that I'm trying to write unit test case for is as follows -
#RequestMapping("${rest.base.path}/plugin")
public class Controller {
.
.
.
}
The Unit test case is setup -
#RunWith(MockitoJUnitRunner.class)
public class ControllerTest {
#Autowired
private MockMvc mvc;
#InjectMocks
Controller dataController;
#Mock
PluginService pluginService;
#Test
public void createFiles() throws Exception {
this.mvc = MockMvcBuilders.standaloneSetup(dataController).build();
mvc.perform(MockMvcRequestBuilders.get("/dc/plugin")
.contentType(MediaType.APPLICATION_JSON));
}
On running the unit test, it's unable to resolve the placeholder ${rest.base.path} as I'm not loading the Spring Context. I tried setting the System.setProperty("rest.base.path", "/api") without any success. Is there anyway I can assign value to that placeholder without removing #RunWith(MockitoJUnitRunner.class)?
The key here is to fill placeholder yourself calling StandaloneMockMvcBuilder.addPlaceholderValue
As the documentation states:
In a standalone setup there is no support for placeholder values embedded in request mappings. This method allows manually provided placeholder values so they can be resolved.
So, the following simple snippet should work for you
public class TestController {
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(new Controller()).addPlaceHolderValue("rest.base.path", "dc")
.setControllerAdvice(new ExceptionMapper())
.setMessageConverters(new MappingJackson2HttpMessageConverter(new ExtendedObjectMapper())).build();
}
#Test
public void testGet() throws Exception {
mockMvc.perform(get("/dc/plugin").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk());
}}
For sure, you can achieve the same autowiring your controller.
I am writing JUnits for my Spring controller. In one of the cases, I am testing the Exception catched within the controller. I have mocked the service to throw a RuntimeException. Other test checks the successful output.
Individually both tests work, but when I execute both together, if the first test executes first, then the second test too throw RuntimeException. Is there anything that I need to deregister the mocked method?
(Please ignore the syntax)
class UserController {
#Autowired
UserService service;
#RequestMapping("...")
public ResponseEntity getUser(){
try{
User user = service.getUserAttributes();
return new ResponseEntity(user, HttpStatus.OK);
}
catch(Exception e){
return new ResponseEntity("Eror", HttpStatus.BAD_REQUEST);
}
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes=TestConfig.class)
public class UserControllerDocumentation {
#Autowired
private WebApplicationContext webApplicationContext;
private RestDocumentationResultHandler document;
private MockMvc mockMvc;
#Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
#Autowired
UserService userService;
#Before
public void setUp(){
this.document = document("{method-name}", preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()));
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.apply(documentationConfiguration(this.restDocumentation)).alwaysDo(document)
.build();
}
#Test
public void textError() throws Exception {
when(userService.getUserAttributes(anyInt())).thenThrow(new RuntimeException());
this.mockMvc.perform(get("/user/xxx")
.accept("application/xml"))
.andExpect(status().isBadRequest());
}
#Test
public void textUser() throws Exception {
when(userService.getUserAttributes(anyInt())).thenReturn(mockUserObj);
this.mockMvc.perform(get("/user/10")
.accept("application/xml"))
.andExpect(status().isOk());
}
Maybe you should reset mocked object before you run each test.
You can do it using reset method.
Here are more details
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#17
To run this before each class you can use #Before annotation.
I don't know how #Autowired works, but obviously: your class has one instance of userService; and your textError() testcase configures that to throw an exception. And when your second test runs, that "behavior specification" is still in place and throws on you.
Thus, I see two options:
a) you use your "after" or "before" methods to reset the userService
b) each test method gets its own userService
Turns out that I need to return something for the mocked method even after throwing an exception. Added thenReturn(null) after thenThrow
#Test
public void textError() throws Exception {
when(userService.getUserAttributes(anyInt())).thenThrow(new RuntimeException()).thenReturn(null);
this.mockMvc.perform(get("/user/xxx")
.accept("application/xml"))
.andExpect(status().isBadRequest());
}