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"));
}
}
Related
I am having dificulties with using MockMvc.
Here I have simple Service and controller classes:
Service:
#Slf4j
#Service
public class EmployeeService {
//...
public Employee GetSample() {
//...
//filling Employee Entities
return new Employee(
"Harriet"
, "random"
, 25);
}
}
controller:
#RestController
#RequestMapping(value = "/info")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
#Validated
public class EmployeeController {
private final EmployeeService employeeService;
#PostMapping("/GetEmployee")
public ResponseEntity<Employee> GetEmployee() {
Employee employee = employeeService.GetSample();
return new ResponseEntity<>(employee, HttpStatus.OK);
}
}
Test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class EmployeeTestCase {
private MockMvc mockMvc;
#InjectMocks
private EmployeeController EmployeeController;
#Mock
private EmployeeService employeeService;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(employeeController).build();
}
#Test
public void getEmployee() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/info/GetEmployee")).andDo(print());
}
}
when I try to use MockMvc I get null body. It seems employee is null. But I didn't understand why.
I thought that when test uses perform, it should initialise employee and later on it should't be null.
I tried to cut the code as much as possible. I hope it is not long.
Note : also tried to use Mockito.when(employeeController.GetEmployee()).thenCallRealMethod();
The #SpringBootTest annotation loads the complete Spring application
context. That means you do not mock your layers
(Services/Controllers).
If you wanted to test specific layers of your application, you could look into test slice annotations offered by Springboot: https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html
In contrast, a test slice annotation only loads beans required to test a particular layer. And because of this, we can avoid unnecessary mocking and side effects.
An example of a Test Slice is #WebMvcTest
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = HelloController.class,
excludeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)
}
)
public class HelloControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void hello() throws Exception {
String hello = "hello";
mvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string(hello));
}
#Test
public void helloDto() throws Exception {
String name = "hello";
int amount = 1000;
mvc.perform(
get("/hello/dto")
.param("name", name)
.param("amount", String.valueOf(amount)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is(name)))
.andExpect(jsonPath("$.amount", is(amount)));
}
}
However if you really wanted to load up the SpringBoot Application context, say for an Integration Test, then you have a few options:
#SpringBootTest
#AutoConfigureMockMvc
public class TestingWebApplicationTest {
#Autowired
private MockMvc mockMvc;
#Test
public void shouldReturnDefaultMessage() throws Exception {
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("Hello, World")));
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class AuctionControllerIntTest {
#Autowired
AuctionController controller;
#Autowired
ObjectMapper mapper;
MockMvc mockMvc;
#Before
public void setUp() throws Exception {
System.out.println("setup()...");
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void create_ValidAuction_ShouldAddNewAuction() throws Exception {
final Auction auction = new Auction(
"Standing Desk",
"Stand up desk to help you stretch your legs during the day.",
"Johnnie34",
350.00);
mockMvc.perform(post("/auctions")
.contentType(MediaType.APPLICATION_JSON)
.content(toJson(auction)))
.andExpect(status().isCreated());
}
}
Lets say you don't want to load up any layers at all, and you want to mock everything, such as for example a Unit Test:
#RunWith(MockitoJUnitRunner.class)
class DemoApplicationTest {
#Mock
private UserRepository userRepository;
private Demo noneAutoWiredDemoInstance;
#Test
public void testConstructorCreation() {
MockitoAnnotations.initMocks(this);
Mockito.when(userRepository.count()).thenReturn(0L);
noneAutoWiredDemoInstance = new Demo(userRepository);
Assertions.assertEquals("Count: 0", noneAutoWiredDemoInstance.toString());
}
}
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);
I have a problem with testing the REST API using MOCKITO
I have an example of the rest controller code:
#RestController
#RequestMapping(path = "api/workers")
public class WorkOfferController {
#Autowired
private WorkerService workerService;
#PostMapping(value = "/lists", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity searchWorker(#RequestBody RecruitmentSearchRequest recruitmentSearchRequest, #RequestParam("page") int page, #RequestParam("size") int size,) throws NoSuchFieldException {
System.err.print('WorkerController');
return workerService.getWorkers(recruitmentSearchRequest, page, size);
}
}
And the right service for it:
#Service
#Transactional
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class WorkerService {
private final WorkerRepistory workerRepository;
private final UserRepository userRepository;
public ResponseEntity getWorkers(RecruitmentSearchRequest recruitmentSearchRequest,int pageNumber, int size) throws NoSuchFieldException {
System.err.print('WorkerService');
...
}
}
I want to test whether everything is okay under the url with the right data. I do not want to use this database because I prefer Mockito.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Appp2workApplication.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PharmacyWorkOfferRestDocsTests {
private MockMvc mockMvc;
#InjectMocks
private WorkOfferController workOfferController;
#Mock
private WorkerService workerService;
#Mock
UserRepository userRepository;
#Mock
WorkerRepistory workerRepository;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(workOfferController).build();
}
#Test
public void searchWorkOfferListWithParameters() throws Exception {
String payload = "{\n" +
"\t\"name\":\"tomek\",\n" +
"\t\"searchFromSalary\":5,\n" +
"}";
Mockito.doNothing().when(userRepository.save(Mockito.anyObject()));
Mockito.when(searchService.getRecruitmentOfferJobListWithParameters(Mockito.anyObject())).thenReturn(list);
this.mockMvc.perform(post("/api/workers/lists?page=0&size=20").contentType(MediaType.APPLICATION_JSON).content(payload))
.andExpect(status().isOk());
}
}
And I have a problem that with this approach the test comes to me in the controller and displays "WorkerController" but I do not want to enter the service from this controller, and it returns 200, but it really only came to the controller and that's it. This is probably because WorkerService is as Mock but I tried to give it as eg Autowired or InjectMock and it is still the same.
What do I do wrong that I enter into the controller but I do not want to use this controller for the appropriate service?
I have a Controller which is publishing an event
#RestController
public class Controller
{
#Autowired
private ApplicationEventPublisher publisher;
#GetMapping("/event")
public void get()
{
publisher.publishEvent(new Event());
}
}
Now I want to test that the event is published. First I tried to #MockBean the ApplicationEventPublisher and verify the method call. But this does not work according to https://jira.spring.io/browse/SPR-14335
So I am doing it like this:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = Controller.class)
public class ControllerTest
{
#Autowired
private MockMvc mockMvc;
#Test
public void getTest() throws Exception
{
this.mockMvc.perform(get("/").contentType(MediaType.APPLICATION_JSON)
.andExpect(status().isOk());
assertNotNull(Listener.event);
}
#TestConfiguration
static class Listener
{
public static Event event;
#EventListener
void listen ( Event incoming )
{
event = incoming;
}
}
}
Is there an easier way for this common use case?
You can do it like this
#RunWith(SpringRunner.class)
public class ControllerTest {
private MockMvc mockMvc;
#MockBean
private ApplicationEventPublisher publisher;
#Before
public void setup() {
Controller someController= new Controller(publisher);
mockMvc = MockMvcBuilders.standaloneSetup(someController).build();
}
#Test
public void getTest() throws Exception
{
ArgumentCaptor<Event> argumentCaptor = ArgumentCaptor.forClass(Event.class);
doAnswer(invocation -> {
Event value = argumentCaptor.getValue();
//assert if event is correct
return null;
}).when(publisher).publishEvent(argumentCaptor.capture());
this.mockMvc.perform(get("/").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
verify(publisher, times(1)).publishEvent(any(Event.class));
}
}
And also change Field Injection to Constructor Injection in your controller class(It is a good practice).
#RestController
public class Controller
{
private ApplicationEventPublisher publisher;
#Autowired
public Controller(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
....
}
im facing the same problem, and for now i solve the problem using TestConfiguration:
#SpringBootTest
class MyUseCaseIT {
#Autowired private ApplicationEventPublisher publisher;
#Autowired private MyService service;
#Test
void callUseCase() {
var event = mock(MyEvent.class);
doNothing().when(publisher).publishEvent(event);
service.useCase();
verify(publisher).publishEvent(event);
}
#TestConfiguration
static class MockitoPublisherConfiguration {
#Bean
#Primary
ApplicationEventPublisher publisher() {
return mock(ApplicationEventPublisher.class);
}
}
Another possiblity would be that you replace the ApplicationEventPublisher instance on your controller with the mock instance using reflection in your test:
public class ControllerTest {
...
// Collect your controller
#Autowired
private Controller controller;
// Use the mock publisher
#MockBean
private ApplicationEventPublisher publisherMock;
// E.g. in setup set the mock publisher on your controller
#Before
public void setup() {
ReflectionTestUtils.setField(controller, "publisher", publisherMock);
}
...
I have a custom ConstraintValidator:
#Component
public class FooValidator implements ConstraintValidator<FooAnnotation, String> {
#Inject
private FooRepository fooRepository;
#Override
public void initialize(FooAnnotation foo) {
}
#Override
public boolean isValid(String code, ConstraintValidatorContext context) {
Foo foo = fooRepository.findByCode(code);
//My code
}
}
In my Junit tests and MockMVC I call the url but fooRepository bean validator is always null.
How I can inject it in my test controller? I tried to create a mock repository but it is also null.
My source code test:
public class FooControllerTest {
#InjectMocks
private FooController fooController;
private MockMvc mockMvc;
#Before
public void setup() {
// Process mock annotations
MockitoAnnotations.initMocks(this);
// Setup Spring test in standalone mode
this.mockMvc = MockMvcBuilders.standaloneSetup(fooController)
.setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
.build();
}
#Test
public void testSave() throws Exception {
Foo foo = new Foo();
// given
//My code...
// when
// then
// with errors
this.mockMvc.perform(post("/foo/update")
.param("name", "asdfasd")
.sessionAttr("foo", foo))
.andExpect(model().hasErrors())
.andExpect(model().attributeHasFieldErrors("foo", "name"));
}
}
You should add a #ContextConfiguration to your test, among other things.