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.
Related
I need to write unit tests for a Spring Controller class.
The setup is like this:
#RestController
#RequestMapping("/")
public class MyCustomController {
#Autowired
private MagicWriter magicWriter;
#Autowired
private MagicUpdater magicUpdater;
#RequestMapping(path = "/", method = RequestMethod.POST)
public String postMagicMethod(#RequestParam(name = "SomeParam") String param1) {
var magicHandler = new MagicHandler(magicWriter, magicUpdater);
return magicHandler.doSomeMagic();
}
}
From my JUnit test, I need to use #MockBean for magicWriter and magicUpdater class.
So far I could not find anything constructive.
Here is my Unit test
#SpringJUnitConfig
#WebMvcTest(value= MyCustomController.class)
public class MyCustomControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MagicWriter magicWriter;
#MockBean
private MagicUpdater magicUpdater;
#Autowired
private WebApplicationContext webApplicationContext;
#Configuration
static class Config {
#Bean
MyCustomController dispatchController() {
return new MyCustomController();
}
}
#Test
void basicTest() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
HttpHeaders headers = new HttpHeaders();
// Added some http headers
String uri = "/";
RequestBuilder request = MockMvcRequestBuilders.request(HttpMethod.POST, uri, headers);
MvcResult result = mockMvc.perform(request).andReturn();
assertThat(result.getResponse().getContentAsString()).isEqualTo(expected);
}
}
Convert your #Autowired parameters to be constructor based and not field-based.
#RestController
#RequestMapping("/")
public class MyCustomController {
private MagicWriter magicWriter;
private MagicUpdater magicUpdater;
#Autowired
public MyCustomController(MagicWriter magicWriter, MagicUpdater magicUpdater) {
this.magicWriter = magicWriter;
this.magicUpdater = magicUpdater;
}
// ... rest of your code
}
Then in your test, you just new an instance of this class with your mocks passed in. You're already resigned to using mock beans, so you don't need to whole Spring Context to come along.
// Unit test code example
MyCustomController testObject;
MagicWriter magicWriterMock;
magicUpdater magicUpdaterMock;
#BeforeEach
void setUp() throws Exception {
magicWriterMock = mock(MagicWriter.class);
magicUpdaterMock = mock(MagicUpdater.class);
testObject = new MyCustomController(magicWriterMock, magicUpdaterMock);
}
I have a simple Rest Controller as below
#RestController
public class HealthController {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
private HealthService healthService;
#Autowired
public HealthController(HealthService healthService) {
this.healthService = healthService;
}
#RequestMapping(value = "/health", method = RequestMethod.GET)
public ResponseEntity<?> healthCheck() {
return healthService.checkHealth();
}
}
The service class is below
#Service
public class HealthService {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
public ResponseEntity<?> checkHealth() {
logger.info("Inside Health");
if (validateHealth()) {
return new ResponseEntity<>("Healthy", HttpStatus.OK);
} else {
return new ResponseEntity<>("Un Healthy", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
boolean validateHealth() {
return true;
}
}
The corresponding unit test for the controller class as below
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = HealthController.class)
public class HealthControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HealthService healthService;
#Test
public void checkHealthReturn200WhenHealthy() throws Exception {
ResponseEntity mockSuccessResponse = new ResponseEntity("Healthy", HttpStatus.OK);
when(healthService.checkHealth()).thenReturn(mockSuccessResponse);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/health").accept(
MediaType.APPLICATION_JSON);
MvcResult healthCheckResult = mockMvc
.perform(requestBuilder).andReturn();
Assert.assertEquals(HttpStatus.OK.value(), healthCheckResult.getResponse().getStatus());
}
}
The problem I have is my CustomLogger. Since it has external dependencies am having issues in trying to test this.The same kind of logger is present in my service classes too.
How can I test such a class. I tried the below stuffs
Created a custom class name CustomLoggerForTest under test. Used
ReflectionTestUtils.setField(healthService, "logger", new CustomerLoggerForTest(HealthService.class.getName()));
in the setUp. But it did not help. Using this we cannot set the static fields hence tried even converting them to be non-static
Tried with mocking the CustomLogger in setup as below
mockStatic(CustomLogger.class); when(CustomLogger.getLogger(any())) .thenReturn(new CustomLoggerForTest(HealthController.class.getName()));
But no luck.
Is there anything that am doing wrong that is causing this?
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 writing a simple REST client, my service class has one method using
RestTemplate.getForObject()
I would like to practice testing but I don't really know if we should test the class. I also do not know how to test method that does not do much. Should I write any unit tests?
You can test, that request has been sent. For example you have the next service:
#Service
public void QueryService {
private final RestTemplate restTemplate;
#Autowired
public QueryService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public List<Employee> void makeQuery() {
ResponseEntity<List<Employee>> response = restTemplate.getForObject(
"http://localhost:8080/employees",
EmployeeList.class);
return response.getEmployees();
}
}
For the method makeQuery you can write the next unit test:
#RunWith(MockitoJUnitRunner.class)
public class QueryServiceTest {
#Mock
private RestTemplate restTemplate;
#InjectMocks
private QueryService queryService;
#Test
public void testMakeQueryRequestHasBeenSent() {
List<Employee> result = queryService.makeQuery();
// This string checks that method getForObject has been invoked
verify(restTemplate).getForObject(anyString(), any(Class.class));
}
}
In the future, if you will change makeQuery method and forget to send request using restTemplate, this test will fail.
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();