I am trying to test a controller such that it triggers validation on the request body object based on the validators that I have defined on the object. Below are the relevant classes.
If you notice my test method I am expecting bad request based on the validator defined, but for some reason it directly tries to execute the service, which was not expected and therefore I don't have any mock defined for the service and hence, I get a NullPointerException since the service object is null as it is mocked.
I tried replacing WebMvcTest to SpringBootTest based on some suggestions that I googled and stackoverflowed, but nothing helped. What could be the possible reason?
Request Object
#Validated
public class RequestBean implements Serializable {
private static final long serialVersionUID = -8976713543478074839L;
#NotNull
private Long id;
#NotNull
private String name;
#NotEmpty
private List<Long> statusIds;
// getters and setters
}
Controller
#RestController
public class SomeController {
#Autowired
private SomeService service;
#PostMapping(value = "/resources", produces = MediaType.APPLICATION_JSON_VALUE)
public List<VerDealProductBean> fetchResources(
#Valid #RequestBody RequestBean request)
throws SomeException {
return service.doSomething(request);
}
Controller Test
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
#ActiveProfiles("test")
public class SomeControllerTest {
private MockMvc mockMvc;
#MockBean
private SomeService service;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(SomeController.class)
.build();
}
#Test
public void testFetchResourcesWithNoInput() throws Exception {
String request = "{\"id\": null}";
mockMvc.perform(post("/resources")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(request))
.andDo(print())
.andExpect(status().isBadRequest());
}
}
Maybe you lack a package javax.el
http://hibernate.org/validator/documentation/getting-started/
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b09</version>
</dependency>
Related
I'm making test code in spring boot.
But, my test code doesn't save the data using #Before method.
If i request to '/v1/stay/, it return empty array...
Please can you explain what is wrong with my code?
Here is my test code.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class StayControllerTest {
#MockBean
private StayService stayService;
#Autowired
private MockMvc mockMvc;
// givenStay method is the method generating dummy data
#Before
public void before() {
stayService.save(givenStay1());
stayService.save(givenStay2());
stayService.save(givenStay3());
stayService.save(givenStay4());
stayService.save(givenStay5());
}
#Test
#Transactional
void showStayList() throws Exception {
List<StayReq> original = new ArrayList<>();
original.add(givenStay1());
original.add(givenStay2());
original.add(givenStay3());
original.add(givenStay4());
original.add(givenStay5());
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/v1/stay")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
System.out.println(result.getResponse());
}
}
And below code blocks are my StayController and StayService
#RestController
#ApiV1
#RequiredArgsConstructor
public class StayController {
private final StayService stayService;
private final ApiService apiService;
#GetMapping("/stay")
public ResponseEntity<Response> stayList() {
return apiService.okResponse(stayService.getList());
}
}
#Service
#RequiredArgsConstructor
public class StayService {
private final StayRepository stayRepository;
private final RoomRepository roomRepository;
public List<StayRes> getList() {
return stayRepository.findAll().stream().map(StayRes::new).collect(Collectors.toList());
}
#Transactional
public void save(StayReq stayReq) {
stayRepository.save(stayReq.toEntity());
}
}
You injected a mock, not a 'real' service. If you want to use a 'real' service - you need to replace #MockBean annotation with #Autowired annotation.
Or alternatively - you can configure mock in the test method to return some predefined data.
I've been trying to figure out why my mocked findIngredientsByCategory method is returning null when I have when(controller.findIngredientsByCategory(any()).thenReturn(Collections.emptyList()). This implementation works for the findAll method works.
Below is my implementation for my unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(IngredientController.class)
#ContextConfiguration(classes = {TestContext.class, WebApplicationContext.class})
#WebAppConfiguration
public class IngredientControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private IngredientController ingredientController;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Autowired
private ObjectMapper mapper;
private static class Behavior {
IngredientController ingredientController;
public static Behavior set(IngredientController ingredientController) {
Behavior behavior = new Behavior();
behavior.ingredientController = ingredientController;
return behavior;
}
public Behavior hasNoIngredients() {
when(ingredientController.getAllIngredients()).thenReturn(Collections.emptyList());
when(ingredientController.getIngredientsByCategory(any())).thenReturn(Collections.emptyList());
when(ingredientController.getIngredientById(anyString())).thenReturn(Optional.empty());
return this;
}
}
#Test
public void getIngredientsByCategoryNoIngredients() throws Exception {
Behavior.set(ingredientController).hasNoIngredients();
MvcResult result = mvc.perform(get("/ingredients/filter=meat"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andReturn();
String content = result.getResponse().getContentAsString();
System.out.println(content);
}
And below is the implementation for the controller:
#RestController
#RequestMapping("/ingredients")
public class IngredientController {
#Autowired
private IngredientRepository repository;
#RequestMapping(value = "/filter", method = RequestMethod.GET)
public List getIngredientsByCategory(#RequestParam("category") String category) {
return repository.findByCategory(category);
}
}
I'm not sure why the mock controller is returning null with this request, when I tell it to return an empty list. If someone could please help with this I would greatly appreciate it! Thanks.
Th request path in test is "/ingredients/filter=meat", but it should be "/ingredients/filter?category=meat". So, it seem that getIngredientsByCategory was not called.
The MockMvc actually will call the IngredientController that is bootstrapped and created by the Spring Test framework but not call the mocked IngredientController that you annotated with #MockBean, so all the stubbing that you made will not be called.
Actually, the point of #WebMvcTest is to test #RestController and its related Spring configuration is configured properly , so a real instance of IngredientController is necessary to create rather than using a mocked one. Instead , you should mock the dependencies inside IngredientController (i.e IngredientRepository).
So , the codes should looks like:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(IngredientController.class)
#ContextConfiguration(classes = {TestContext.class, WebApplicationContext.class})
#WebAppConfiguration
public class IngredientControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private IngredientRepository ingredientRepository;
#Test
public void fooTest(){
when(ingredientRepository.findByCategory(any()).thenReturn(Collections.emptyList())
//And use the MockMvc to send a request to the controller,
//and then assert the returned MvcResult
}
}
I am trying to write tests for my rest controller and getting a NullPointerException whenever I attempt to perform actions on the MockMvc instance.
My project is structured as follows:
POJO:
#Entity
public class Pair {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String a;
private String b;
//getters and setters...
}
Rest Controller:
#RestController
public class PairController {
#Autowired
private PairServiceImpl pairService;
#RequestMapping(value = "/pair", method = RequestMethod.POST)
public Pair addPair(String a, String b) {
Pair newPair = new Pair();
newPair.setA(a);
newPair.setB(b);
return pairService.addNewPair(newPair);
}
#RequestMapping(value = "/pair", method = RequestMethod.GET)
public List<Pair> getPairs() {
return pairService.getPairs();
}
}
Service layer:
#Service
public class PairServiceImpl implements PairService {
#Autowired
private PairRepositoryImpl pairRepository;
public Pair addNewPair(Pair newPair) {
return pairRepository.save(newPair);
}
public List<Pair> getPairs() {
return pairRepository.findAll();
}
}
Repository:
public interface PairRepositoryImpl extends JpaRepository<Pair, Long> {
}
And I want to test the PairController API endpoints:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {DemoApplication.class, DatabaseConfig.class})
#AutoConfigureMockMvc
#ContextConfiguration(classes = {PairController.class})
public class PairControllerTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private PairService pairService;
#Test
public void addPairTest() {
Pair testPair = new Pair();
testPair.setA("a");
testPair.setB("b");
ObjectMapper objectMapper = new ObjectMapper();
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/pair").accept(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(testPair))).andReturn();
//The line above throws an exception
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
}
}
If I dont add the #ContextConfiguration the tests cannot find my endpoint.
I tried to log the a and b value when the addPair method was invoked, and both values were null. You can also see that I add a custom database config class "DatabaseConfig", which contains a H2 embedded database datasource method such that the tests do not use the production database. The #EnableJpaRepositories annotation is present in this class and it points to the repository shown above.
I have tried to juggle around many different annotations but they all have the same end result: null values in the controller method.
I have also tried to manually constructing the MockMvc instance using #Autowired on the WebApplicationContext and using that to initialize the MockMvc instance in a method with the #Before annotation - but the end result was the same.
I have made a comment below the line that throws an exception, located in the PairControllerTests class.
So if I run the application and test it using Postman and the production database the endpoints work and the data is persisted and retrieved properly. This issue only happens during testing.
I would like to test my REST controller using MockMvc but I always get an empty body response.
My AccountControllerUnitTest looks like this:
#RunWith(SpringRunner.class)
#WebMvcTest(AccountRestController.class)
public class AccountRestControllerUnitTests {
#Autowired
private MockMvc mockMvc;
#MockBean(name = "dtoUtil")
private DtoUtil dtoUtil;
#MockBean
private AccountService accountService;
#Test
public void canRetrieveAll() throws Exception {
when(accountService.findAll())
.thenReturn(Collections.singletonList(AccountTestFixture.createAccount()));
this.mockMvc.perform(get("/accounts")).andDo(print())
.andExpect(status().isOk());
}}
The accountService when mock works as expected, calling accountService.findAll() returns a list with a single account element.
With my used AccountRestController being:
#RestController
#AllArgsConstructor
#RequestMapping("/accounts")
public class AccountRestController {
private AccountService accountService;
#Qualifier(dtoUtil)
private DtoUtil dtoUtil;
#GetMapping
public List<AccountDto> getAccounts() {
return accountService.findAll().stream()
.map(dtoUtil::mapToDto)
.collect(Collectors.toList());
}
Running the test will result in a MockHttpServletResponse with a body that is null.
It works flawlessly for my normal (non-rest that has a model) controller, the only difference being that it doesn't use the DtoUtil.
Could that be the reason for it constantly returning null?
edit:
The DtoUtil:
#Component
public class DtoUtil{
#Autowired
private ModelMapper mapper;
public AccountDto mapToDto(Account account) {
return modelMapper.map(account, AccountDto.class);
}
}
Add to your test
when(dtoUtil.mapToDto(...)).thenCallRealMethod();
I am trying to run a Spring MVC Test but keep getting this exception.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
The exception is occuring because the autowired dependency,
#Autowired
private AccountService accountService;
is not getting injected in the test (works fine outside of the test).
Can anyone help me with this. Here is my code:
//AccountControllerITest Class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MockServletContext.class)
#WebAppConfiguration
public class AccountControllerITest {
private MockMvc mvc;
ObjectMapper om;
#Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
#Test
public void getAccounts() throws Exception {
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get("/api/accounts"))
.andExpect(status().isOk())
.andReturn();
}
}
}
//AccountController
#RestController
#RequestMapping("/api/accounts")
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Set<AccountInfo>> getAccounts(#RequestParam(value = "firstName", required = false) String firstName,
#RequestParam(value = "surName", required = false) String surName) {
Set<AccountInfo> accounts = accountService.getAccounts(firstName, surName);
return new ResponseEntity<>(accounts, HttpStatus.OK);
}
}
Thanks for the help!
Because you are using standalone setup: mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();. If you create controller via new AccountController(), Spring doesn't have a chance to inject accountService as it doesn't control instances creation and wiring.
You have two options:
Switch your test to unit test and do not use SpringJUnit4ClassRunner nor MockServletContext at
all. You can use #InjectMocks to inject private accountService:
public class AccountControllerITest {
private MockMvc mvc;
ObjectMapper om;
#Mock
private AccountController accountController = new AccountController();
#InjectMocks
private AccountService accountService = new Mockito.mock(AccountService.class);
#Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(accountController).build();
}
There is also enhancement you can apply to your controller. Replace field injection with constructor injection and you can pass accountService to controller via constructor in test. This way you don't need to use #InjectMocks at all.
Use webAppContext setup:
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
You need to configure your test to inject your autowired properties. See the following code block:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class AccountControllerITest {
// ... your test code
#Configuration
public static class YourConfiguration {
#Bean
AccountService accountService() {
// init and return your AccountService
}
}
}
You can init your AccountService with Mockito.