I got this unit test code to test my spring mvc view, its look like failing to initialize the service.
#AutoConfigureMockMvc
#ContextConfiguration(classes = { TestAppContext.class })
#WebMvcTest
#Transactional
class BillEntryControllerTest {
#Autowired
private BillEntryService billEntryService;
#Autowired
private MockMvc mockMvc;
#BeforeEach
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new BillEntryController())
.build();
}
#Test
public void checkUpdateBill() throws Exception {
billEntryService = Mockito.mock(BillEntryServiceImpl.class);
doNothing().when(billEntryService).addOrUpdateBill(any(BillEntry.class));
this.mockMvc
.perform(MockMvcRequestBuilders.post("/bill-entry/saveBillEntry").accept(MediaType.TEXT_HTML)
.param("amount", "10.0")
.param("owner", "User")
.param("property", "Prop")
.param("receiptNumber", "ABC")
.param("accountName", "AC")
.param("billerName", "BN")
.param("datePaid", "20/10/2022")
.param("dateDue", "20/10/2022"))
.andExpect(model().errorCount(0)).andExpect(status().isOk());
}
}
And getting following error
Caused by: java.lang.NullPointerException: Cannot invoke "org.de.service.BillEntryService.addOrUpdateBill(org.de.model.BillEntry)" because "this.billEntryService" is null
at org.de.controller.BillEntryController.saveBillEntry(BillEntryController.java:157)
#PostMapping("/saveBillEntry")
public String saveBillEntry(Model model, #Valid #ModelAttribute("billEntry") BillEntryFormDto dto,
BindingResult theBindingResult) {
BillEntry billEntry = new BillEntry();
if (dto.getBillId()!=null && !dto.getBillId().isEmpty()) {
logger.debug("biller id " + dto.getBillId());
billEntry = billEntryService.getBillEntryById(Integer.parseInt(dto.getBillId()));
}
if (theBindingResult.hasErrors()) {
logger.error("has errors ");
getDefault(model);
return "bill-entry-form";
}
//updating the form attributes
billEntry.setAccountName(dto.getAccountName());
billEntry.setAmount(Double.parseDouble(dto.getAmount().replaceAll("[^\\d.]", "")));
billEntry.setBillerName(dto.getBillerName());
billEntry.setDateDue(FormatHelper.getDateFromString(dto.getDateDue()));
billEntry.setDatePaid(FormatHelper.getDateFromString(dto.getDatePaid()));
billEntry.setProperty(dto.getProperty());
billEntry.setReceiptNumber(dto.getReceiptNumber());
billEntry.setOwner(dto.getOwner());
logger.info("attempt to save/update bill entires " + billEntry.getBillId());
logger.debug("entry " + billEntry);
//failing at here (line 157)
billEntryService.addOrUpdateBill(billEntry);
return "redirect:"+ dto.getRedirect();
}
I try to mock the BillEntryService but that didn't help me ether. Any tips on how to fix it or what I'm doing wrong?
This is because your mocked billEntryService is not injected into the controller under test. As a result, that field is not initialized (null) in the controller during the test.
To fix that, the billEntryService must be annotated with #MockBean instead of #Autowired in the test. That will inject the mocked version of BillingEntryService into the controller. And you shouldn't initialize that mocked billEntryService using Mockito.mock(...) afterwards. The mockMvc shouldn't be initialized either, because you are using #WebMvcTest that already injects a proper mockMvc and you are autowiring it. So, the #BeforeEach method is also to be removed.
Given the above, the relevant part of the test will look like this:
#ContextConfiguration(classes = { TestAppContext.class })
#WebMvcTest
#Transactional // doesn't seem necessary here but I haven't touched this
class BillEntryControllerTest {
#MockBean
private BillEntryService billEntryService;
#Autowired
private MockMvc mockMvc;
#Test
public void checkUpdateBill() throws Exception {
doNothing().when(billEntryService).addOrUpdateBill(any(BillEntry.class));
mockMvc.perform...
}
Also, you can find this answer quite helpful.
Related
Spring Boot here. I currently have the following REST controller:
#RestController
public class FizzbuzzController {
private final FizzbuzzService FizzbuzzService;
public FizzbuzzController(FizzbuzzService FizzbuzzService) {
this.FizzbuzzService = FizzbuzzService;
}
#PostMapping("/Fizzbuzzs/{fizzbuzzId}")
public ResponseEntity<FizzbuzzDTO> addFizzbuzz(#RequestParam("files") List<MultipartFile> files,
#PathVariable String fizzbuzzId) throws IOException {
FizzbuzzDTO fizzbuzzDTO = fizzbuzzService.store(files, fizzbuzzId);
return ResponseEntity.status(HttpStatus.OK).body(fizzbuzzDTO);
}
}
I would like to write an integration test for it that:
Mocks or stubs an HTTP request to the URL; and
Allows me to inject the FizzbuzzController (under test) with a mocked FizzbuzzService or the real thing; and
Allows me to inspect the HTTP response coming back from the method (check status code, check response entity, etc.)
My best attempt thus far:
#WebMvcTest(FizzbuzzController.class)
#EnableConfigurationProperties
#AutoConfigureMockMvc
public class FizzbuzzControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private FizzbuzzService FizzbuzzService;
#Test
public void should_store_fizzbuzz_files() throws Exception {
// I can't even get the test to run
assertTrue(1 == 1);
}
}
When I run this, the test fails to run and it is clear (looking at the logs) that Spring is loading the entire application context of my app, whereas I just want it to isolate the context to this test class, the main FizzbuzzController class, and anything in the dependency tree underneath it.
Can anyone spot where I'm going awry?
You need another context for testing. I'm suggesting you to have a separate Test config:
#TestConfiguration
#Slf4j
#EnableJpaRepositories("tth.patientportal.repository")
public class TestConfig { // bean configs goes here for testing if you need to change
// context}
and in a controller test build the context like below:
#RunWith(SpringRunner.class)
#AutoConfigureTestEntityManager
#SpringBootTest
#TestPropertySource("classpath:application-unittest.properties")
#ContextConfiguration(classes = {TestConfig.class})
public class RestControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup()
{
mockMvc = MockMvcBuilders.
webAppContextSetup(webApplicationContext)
.build();
}
#Test
public void shouldReturnRegisteredUser() throws Exception {
this.mockMvc.
perform(MockMvcRequestBuilders
.post("url")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").exists());
}
}
I have a Spring application,
and I've created this test:
#RunWith(SpringRunner.class)
#SpringJUnitWebConfig(locations = {
"classpath:testDatabaseContext.xml",
"classpath:testServicesContext.xml",
"classpath:backoffice-servlet.xml"
})
public class UserControllerTests {
#Autowired
private MockMvc mockMvc;
#Before
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
..
}
but when I start the test I got this error:
rg.junit.runners.model.InvalidTestClassError: Invalid test class 'com.pastis.UserControllerTests':
1. Method setup() should be public
2. Method setup should have no parameters
Here an example:
RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = MyWebConfig.class)
public class CustomerControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup () {
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
this.mockMvc = builder.build();
}
#Test
public void testUserController () throws Exception {
ResultMatcher ok = MockMvcResultMatchers.status()
.isOk();
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/customers");
this.mockMvc.perform(builder)
.andExpect(ok);
}
}
So I explain the output of the exception:
Add the modifier public to your method setup, otherwise JUnit can't invoke it
Remove the parameter from the method, #Before, #After and such don't allow parameters.
How to setup MockMvc correctly is another question. Recent Spring and additional annotations regarding web scope initialization and -behavior leave the scope of this answer. (This also requires more clarification, for example which JDK, which Spring or Spring Boot… XML configuration, dbunit and JUnit 4 suggest a legacy context.)
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 have two integration test in my program and unfortunately both doesn't work. I don't know this is good idea to write both problem in one case but i try.
Firstly i show my db integration test :
#RunWith(SpringRunner.class)
#DataJpaTest
public class TeamDatabaseIntegrationTest {
#MockBean
private TeamRepository teamRepository;
#Autowired
private TestEntityManager testEntityManager;
#Test
public void testDb() {
Team team = new Team(1L, "teamName", "teamDescription", "krakow", 7);
Team team2 = new Team(2L, "teamName", "teamDescription", "krakow", 7);
testEntityManager.persist(team);
testEntityManager.flush();
Iterable<Team> teams = teamRepository.findAll();
assertThat(teams).hasSize(2).contains(team, team2);
}
In this test i add 2 elements to my db and expect that this test is ok but it return this :
java.lang.AssertionError:
Expected size:<2> but was:<0> in:
<[]>
In my second test i want to test controller method show all elements.
This is my method in cotroller:
#GetMapping("/teams")
public List<TeamDto> findAll() {
return teamService.findAll();
}
My test method for this look like this :
#SpringJUnitWebConfig(classes = CrewApplication.class)
public class TeamControllerMethodIntegrationTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setup() throws Exception
{
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
MockitoAnnotations.initMocks(this);
}
#Test
void getAccount() throws Exception {
this.mockMvc.perform(get("/teams")
.accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$version").value(null))
.andExpect(jsonPath("$name").value("Apacze"))
.andExpect(jsonPath("$createOn").value(null))
.andExpect(jsonPath("modifiedOn").value(null))
.andExpect(jsonPath("$description").value("grupa programistow"))
.andExpect(jsonPath("$city").value("Włocławek"))
.andExpect(jsonPath("$headcount").value(null));
}
}
In this case i have other error.
java.lang.NullPointerException
at com.softwaremind.crew.people.integrationTest.TeamControllerMethodIntegrationTest.getAccount
I fight with this tests over week and i really have no idea how to repair this.
In your first case replace
#MockBean
private TeamRepository teamRepository;
with
#Autowired
private TeamRepository teamRepository;
(you cannot use mock and expect it to return values from in memory db)
For you second test. Remove #SpringJUnitWebConfig and annotate it with
#RunWith(SpringRunner.class)
#WebMvcTest()
and add Autowired to MockMvc
#Autowired
private MockMvc mockMvc;
EDIT
Additionally remove your setup() method as everything should be already configured. (and then you also don't need webApplicationContext attribute) and assert that the Test annotation you are using is org.junit.Test (check your imports)
I am trying to write tests for my Spring MVC web application.
I have successfully configured a MockMvc object and can execute preform() operations, and can verify that my controller methods are being called.
The issue I am experiencing has to do with passing in a UserDetails object to my controller methods.
My controller method signature is as follows:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView ticketsLanding(
#AuthenticationPrincipal CustomUserDetails user) {
...
}
During the tests, user is null (which is causing a NullPointerException due to my code.
Here is my test method:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
#Test
public void ticketsLanding() throws Exception {
// testUser is populated in the #Before method
this.mockMvc.perform(
get("/tickets").with(user(testUser))).andExpect(
model().attributeExists("tickets"));
}
So my question is how do I properly pass a UserDetails object into my MockMvc controllers? What about other, non-security related objects such as form dtos?
Thanks for your help.
You need to init the security context in your unit test like so:
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
I use the following setup:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = {
"classpath:/spring/root-test-context.xml"})
public class UserAppTest implements InitializingBean{
#Autowired
WebApplicationContext wac;
#Autowired
private FilterChainProxy springSecurityFilterChain;
// other test methods...
#Override
public void afterPropertiesSet() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.addFilters(springSecurityFilterChain)
.build();
}
}