Testing Spring Controller with Mockito - spring

I am trying to learn to test using Spring 5, Mockito, and JUnit 5.
I have a small normal controller class and its test is as below:
#Controller
#RequestMapping("/customer")
public class CustomerController {
#Autowired
CustomerService customerService;
#Autowired
CustomerForm customerForm;
#GetMapping("/index")
public ModelAndView index(){
String customerName = customerService.getCustomerById(14).getFirstname(); <-- Giving me error here
customerForm.setCustomerName(customerName);
ModelAndView modelAndView = new ModelAndView("customer/pages/customer/Home");
modelAndView.addObject("customerForm", customerForm);
return modelAndView;
}
}
#ExtendWith(MockitoExtension.class)
class CustomerControllerTest {
#InjectMocks
CustomerController customerController;
#Mock
CustomerServiceImpl customerService;
#Mock
CustomerForm customerForm;
Customer customer;
String customerName;
#SuppressWarnings("deprecation")
#BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
customer = new Customer(14, "John");
customerName = "John";
}
#Test
void testIndex() {
int customerId = 14;
when(customerService.getCustomerById(customerId).getFirstname()).thenReturn(customerName); <-- Giving me error here, NullPointerException
customerForm.setCustomerName(customerName);
ModelAndView mav = customerController.index();
assertEquals( customerForm, mav.getModel().get("customerForm"));
}
}
Error:
java.lang.NullPointerException
at com.primis.controller.CustomerControllerTest.testIndex(CustomerControllerTest.java:66)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
When I run this test, I am getting NullPointerException as shown.
Please can someone point me in the right direction, what I am doing wrong.
Thank you

You have to mock customerService.getCustomerById(customerId) first, otherwise it will return null and, in this case, throw NPE.

You are mocking the wrong thing:
when(customerService.getCustomerById(customerId).getFirstname()).thenReturn(customerName);
What you actually need to mock is only the call customerService.getCustomerById(). This means that your testing code should be something like:
#ExtendWith(MockitoExtension.class)
class CustomerControllerTest {
#InjectMocks
CustomerController customerController;
#Mock
CustomerServiceImpl customerService;
#Mock
CustomerForm customerForm;
Customer customer;
String customerName;
#SuppressWarnings("deprecation")
#BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
customer = new Customer(14, "John");
customerName = "John";
}
#Test
void testIndex() {
int customerId = 14;
when(customerService.getCustomerById(customerId)).thenReturn(customer);
customerForm.setCustomerName(customerName);
ModelAndView mav = customerController.index();
assertEquals( customerForm, mav.getModel().get("customerForm"));
}
}
As a side note, I believe that with #ExtendWith(MockitoExtension.class) you don't really need the explicit MockitoAnnotations.initMocks(this); call.

Related

Spring Boot Test model attribute does not exist

Im trying to test a controller, Author Controller, which returns a view with a model. The problem is on the testInitUpdateAuthor() test where its not able to find the model or attribute name specifically. All other methods are fine with their model/attribute tests.
Any advice?
#Slf4j
#Controller
public class AuthorController {
private final AuthorService authorService;
private final String CREATEORUPDATEFORM = "author/createOrUpdateAuthor";
public AuthorController(AuthorService authorService) {
this.authorService = authorService;
}
#GetMapping("/author/{id}/update")
public String updateAuthor(#PathVariable("id") Long id, Model model) {
model.addAttribute("author", authorService.findById(id));
return CREATEORUPDATEFORM;
}
#ExtendWith(MockitoExtension.class)
#SpringBootTest
class AuthorControllerTest {
MockMvc mockMvc;
#Mock
AuthorService authorService;
#InjectMocks
AuthorController authorController;
#BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(authorController).build();
}
#Test
void getIndex() throws Exception {
mockMvc.perform(get("/author/index"))
.andExpect(status().isOk())
.andExpect(view().name("author/index"))
.andExpect(model().attributeExists("authors"));
}
#Test
void testInitUpdateAuthor() throws Exception {
when(authorService.findById(anyLong())).thenReturn(Author.builder().id(1L).build());
mockMvc.perform(get("/author/1/update"))
.andExpect(status().isOk())
.andExpect(view().name("author/createOrUpdateAuthor"))
.andExpect(model().attributeExists("author"));
}
}

how to do unit test validation annotations in spring

I have some annotation in a class such as
public class ProductModel {
#Pattern(regexp="^(1|[1-9][0-9]*)$", message ="Quantity it should be number and greater than zero")
private String quantity;
then in my controller
#Controller
public class Product Controller
private ProductService productService;
#PostMapping("/admin/product")
public String createProduct(#Valid #ModelAttribute("product") ProductModel productModel, BindingResult result)
{
// add println for see the errors
System.out.println("binding result: " + result);
if (!result.hasErrors()) {
productService.createProduct(productModel);
return "redirect:/admin/products";
} else {
return "product";
}
}
Then I am trying to do a test of createProduct from ProductController.
#RunWith(MockitoJUnitRunner.class)
public class ProductControllerTest {
#Autowired
private MockMvc mockMvc;
#Mock
ProductService productService;
#InjectMocks
ProductController productController;
#Mock
private BindingResult mockBindingResult;
#Before
public void setupTest() {
MockitoAnnotations.initMocks(this);
Mockito.when(mockBindingResult.hasErrors()).thenReturn(false);
}
#Test
public void createProduct() throws Exception {
productController = new ProductController(productService);
productController.createProduct(new ProductModel(), mockBindingResult);
Here I do not know how can I add values to the object productmodel and also how can I test the message output of "...number should be greater than zero".
What I was trying to do it was create an object and then assert with values for making it fail or work such as
assertEquals(hello,objectCreated.getName());
Any advice or help will be highly appreciated.
To validate bean annotations you must have the context in execution. You can do this with:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
Then your tests will validate the annotations.
However, if you just want to validate the annotation of model (without another business rules) you can use a validator:
private static ValidatorFactory validatorFactory;
private static Validator validator;
#BeforeClass
public static void createValidator() {
validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.getValidator();
}
#AfterClass
public static void close() {
validatorFactory.close();
}
#Test
public void shouldReturnViolation() {
ProductModel productModel = new ProductModel();
productModel.setQuantity("a crazy String");
Set<ConstraintViolation<ProductModel>> violations = validator.validate(productModel);
assertFalse(violations.isEmpty());
}
Just use setter of your Model
ProductModel productModel = new ProductModel();
productModel.setQuantity("a crazy String");
productModel.setAnotherValueOfThatModel(true);
productController.createProduct(new ProductModel(), mockBindingResult);

Spring MVC release 4.2.6 seems does not inject mock service into the controller when testing controller method

I really searched and followed the steps of creating a unit test class for spring MVC controller, however unit test is running with a green pass flag but the framework uses the original service class and it calls to the database. I mocked the class and used #InjectMocks together with MockitoAnnotations.initMocks(this). Still when the test runs, the controller uses original service object rather than the mocked object. I really appreciate if somebody can help me in this regards.
Here is UserManager(service class), UserRegisterController(controller), TestUserRegisterController (Test class) classes with a picture of the Eclipse package structure
Service :
#Service
public class UserManager {
protected Map<String, String> getAllCertificates() {
Map<String, String> allCertificates = new HashMap<String, String>();
//call to database
return allCertificates;
}
protected User getUser(int userId) {
//create session
User user = session.get(User.class, userId);
//close session
return user;
}
}
Controller :
#Controller
public class UserRegisterController {
#Autowired
private UserManager manager;
#InitBinder
public void initBinder(WebDataBinder binder) {
//do some work
}
#RequestMapping(value = "/user.html", method = RequestMethod.GET)
public ModelAndView getUser(#RequestParam(value="userId", defaultValue="-1") String userId) {
User user1;
user1 = this.manager.getUser(Integer.parseInt(userId));
if (user1 == null) {
user1 = new User();
}
ModelAndView view = new ModelAndView("User", "user1", user1);
view.addObject("allCertificatesMap", this.manager.getAllCertificates());
return view;
}
#ModelAttribute
public void setModelAttribute(Model model) {
model.addAttribute("PageHeader", "lable.pageHeader");
}
}
Test class :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("test-spring-dispatcher-servlet.xml")
#WebAppConfiguration
public class TestUserRegisterController {
#Mock
private UserManager userManager;
#InjectMocks
private UserRegisterController userRegisterController;
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
// Process mock annotations
MockitoAnnotations.initMocks(this);
User user2 = new User();
user2.setUserId(10006);
user2.setUserName("Reza");
user2.setHobby("Quadcopter");
user2.setPhone("4032376295");
when(this.userManager.getUser(10006)).thenReturn(user2);
when(this.userManager.getAllCertificates()).thenReturn(new HashMap<String, String>());
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getUser() {
try {
this.mockMvc.perform(get("/user.html").param("userId", "10006"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/WEB-INF/jsp/User.jsp"))
.andExpect(MockMvcResultMatchers.view().name("User"))
.andExpect(model().attributeExists("allCertificatesMap"));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Package hierarchy
Use #RunWith(MockitoJUnitRunner.class) to get the #InjectMocks and other annotations to work

JdbcTemplate Mockito ClassCastException

I am trying to mock a method of the Spring framework's JdbcTemplate class. The method has the following signature:
public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {...}
The mocking is being done as mentioned below:
when(jdbcTemplate.queryForObject(anyString(), eq(String.class))).thenReturn("data");
However, this call throws the following exception
java.lang.ClassCastException: org.mockito.internal.creation.jmock.ClassImposterizer$ClassWithSuperclassToWorkAroundCglibBug$$EnhancerByMockitoWithCGLIB$$ce187d66 cannot be cast to java.lang.String
at test.DaoJdbcTest.setUp(DaoJdbcTest.java:81)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
Any idea as to what I am doing wrong?
Thanks in advance~
EDIT:
Here's the full code:
public class DaoJdbc extends NamedParameterJdbcSupport {
public String query(String sql) {
return getJdbcTemplate().queryForObject(sql, String.class);
}
}
public class DaoJdbcTest {
#Mock(answer = Answers.RETURNS_SMART_NULLS)
private JdbcTemplate jdbcTemplate;
private DaoJdbc dao;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
dao = new DaoJdbc();
dao.setJdbcTemplate(jdbcTemplate);
Mockito.when(jdbcTemplate.queryForObject(anyString(), Matchers.eq(String.class))).thenReturn("data");
}
#Test
public void testQuery() {
String ret = dao.query("select 'test' from dual");
assertEquals("data", ret);
verify(jdbcTemplate).queryForObject(anyString(), eq(String.class));
}
}
Remove the answer = Answers.RETURNS_SMART_NULLS. Test passes when I remove that. What does that feature do? The default null behavior works fine for me.
As a bonus, you can also use the MockitoJunitRunner to clean up the code a bit...
#RunWith(MockitoJUnitRunner.class)
public class DaoJdbcTest {
#Mock
private JdbcTemplate jdbcTemplate;
#InjectMocks
private DaoJdbc dao;
#Before
public void setUp() throws Exception {
Mockito.when(jdbcTemplate.queryForObject(anyString(), Matchers.eq(String.class))).thenReturn("data");
}
#Test
public void testQuery() {
String ret = dao.query("select 'test' from dual");
assertEquals("data", ret);
verify(jdbcTemplate).queryForObject(anyString(), eq(String.class));
}
}

Setting session attributes for JUnits on Spring 3.2

I'm having trouble setting session attributes for a test. I am using MockMvc to test calls to a controller. The session model has a member attribute on it (representing the person who has logged in). The SessionModel object is added as a session attribute. I was expecting it to be populated in the ModelMap parameter to formBacking method below, but the ModelMap is always empty.
The controller code works fine when running through the webapp, but not in the JUnit. Any idea what I could be doing wrong?
Here is my JUnit Test
#Test
public void testUnitCreatePostSuccess() throws Exception {
UnitCreateModel expected = new UnitCreateModel();
expected.reset();
expected.getUnit().setName("Bob");
SessionModel sm = new SessionModel();
sm.setMember(getDefaultMember());
this.mockMvc.perform(
post("/units/create")
.param("unit.name", "Bob")
.sessionAttr(SessionModel.KEY, sm))
.andExpect(status().isOk())
.andExpect(model().attribute("unitCreateModel", expected))
.andExpect(view().name("tiles.content.unit.create"));
}
and here is the controller in question
#Controller
#SessionAttributes({ SessionModel.KEY, UnitCreateModel.KEY })
#RequestMapping("/units")
public class UnitCreateController extends ABaseController {
private static final String CREATE = "tiles.content.unit.create";
#Autowired
private IUnitMemberService unitMemberService;
#Autowired
private IUnitService unitService;
#ModelAttribute
public void formBacking(ModelMap model) {
SessionModel instanceSessionModel = new SessionModel();
instanceSessionModel.retrieveOrCreate(model);
UnitCreateModel instanceModel = new UnitCreateModel();
instanceModel.retrieveOrCreate(model);
}
#RequestMapping(value = "/create", method = RequestMethod.GET)
public String onCreate(
#ModelAttribute(UnitCreateModel.KEY) UnitCreateModel model,
#ModelAttribute(SessionModel.KEY) SessionModel sessionModel) {
model.reset();
return CREATE;
}
#RequestMapping(value = "/create", method = RequestMethod.POST)
public String onCreatePost(
#ModelAttribute(SessionModel.KEY) SessionModel sessionModel,
#Valid #ModelAttribute(UnitCreateModel.KEY) UnitCreateModel model,
BindingResult result) throws ServiceRecoverableException {
if (result.hasErrors()){
return CREATE;
}
long memberId = sessionModel.getMember().getId();
long unitId = unitService.create(model.getUnit());
unitMemberService.addMemberToUnit(memberId, unitId, true);
return CREATE;
}
}
For your test class add the #WebAppConfiguration annotation and autowire the following as well.(WebApplicationContext and MockHttpSession )
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({ "classpath:springDispatcher-servlet.xml" })
public class MySessionControllerTest {
#Autowired WebApplicationContext wac;
#Autowired MockHttpSession session;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void testUnitCreatePostSuccess() throws Exception {
UnitCreateModel expected = new UnitCreateModel();
expected.reset();
expected.getUnit().setName("Bob");
SessionModel sm = new SessionModel();
sm.setMember(getDefaultMember());
session.setAttribute(SessionModel.KEY, sm);
this.mockMvc.perform(
post("/units/create")
.session(session)
.param("unit.name", "Bob")
.andExpect(status().isOk())
.andExpect(model().attribute("unitCreateModel", expected))
.andExpect(view().name("tiles.content.unit.create"));
}
}

Resources