Mock service in Spring MVC - spring

I have a problem with mocking service in Spring MVC:
#Controller
public class CompanyController {
#Autowired
private CompanyService companyService;
#Autowired
private CompanyRelationService companyRelationService;
#GetMapping({"/", "/companies"})
public String displayCompanies(Model model) {
model.addAttribute("company", new Company());
List<Company> companies = companyService.findAll();
model.addAttribute("companies", companies);
return "companies";
}
}
and test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class CompanyTests {
#Autowired
private WebApplicationContext webApplicationContext;
#Mock
CompanyService companyServiceMock;
private MockMvc mockMvc;
#Before
public void setUp() {
Mockito.reset(companyServiceMock);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
MockitoAnnotations.initMocks(this);
}
#Test
public void shouldListAllCompanies() throws Exception {
Company company1 = new Company("company1", new Address());
Company company2 = new Company("company2", new Address());
when(companyServiceMock.findAll()).thenReturn(Arrays.asList(company1, company2));
mockMvc.perform(get("/companies"))
.andExpect(status().isOk())
.andExpect(view().name("companies"))
.andExpect(model().attribute("companies", hasSize(2)))
.andExpect(model().attribute("companies", hasItem(
allOf(
hasProperty("name", is("company1")))
)))
.andExpect(model().attribute("companies", hasItem(
allOf(
hasProperty("name", is("company2"))
)
)));
}
}
The question is why I get companies from real service instead of mock (company1, company2):
java.lang.AssertionError: Model attribute 'companies'
Expected: a collection containing (hasProperty("name", is "company1"))
but: hasProperty("name", is "company1") property 'name' was "companyFromRealService",
hasProperty("name", is "company1") property 'name' was "CompanyFromRealService2"
Updated Test class, removed setUp and changed #Bean into #MockBean, but remain #SpringBootTest and it works:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class CompanyTests {
#MockBean
private CompanyService companyServiceMock;
#Autowired
private MockMvc mockMvc;
#Test
#WithMockUser(roles = "ADMIN")
public void shouldListAllCompanies() throws Exception {
Company company1 = new Company("company1", new Address());
Company company2 = new Company("company2", new Address());
when(companyServiceMock.findAll()).thenReturn(Arrays.asList(company1, company2));
mockMvc.perform(get("/companies"))
.andExpect(status().isOk())
.andExpect(view().name("companies"))
.andExpect(model().attribute("companies", hasSize(2)))
.andExpect(model().attribute("companies", hasItem(
allOf(
hasProperty("name", is("companyFromRealService1")))
)))
.andExpect(model().attribute("companies", hasItem(
allOf(
hasProperty("name", is("companyFromRealService2"))
)
)));
}
}

First of all, if you are just testing controller slice of your application, you should use #WebMvcTest annotation instead of #SpringBootTest (you can find more information here). You can use it like this : #WebMvcTest(CompanyController.class).
Secondly why are you getting into trouble with MockMvc in setUp() method? You can erase that setUp method as people suggest in comments and #Autowire MockMvc.
Finally, as you are using spring boot, it is better to use #MockBean instead of #Mock which is a wrapped version of it inside the spring library.

Related

Spring Boot Unit Testing MockMvc Null Body

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());
}
}

JUnit not working in Spring Boot with #Autowired annotation

I have a Spring Boot application in which I'm creating REST web services
using the MVC pattern.
I have a controller, service and DAO class and I use the #Autowired annotation for calling methods of the service and DAO layer.
When I create JUnit tests using mockito, the values are going into the controller but from the controller they are not going to the service class.
Here is the code sample:
#WebAppConfiguration
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {AppplicationConfiguration.class})
public class ExternalControllerTest {
private MockMvc mockMvc;
#InjectMocks
private MyController myController;
#MockBean
myService myService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders
.standaloneSetup(myController)
.build();
}
#Test
public void testListCluster() throws Exception {
Input emrInput = new Input();
emrInput.setId("200004773");
emrInput.setName("test");
String expected = "{\"status\":\"Success\",\"message\":\"Success\",\"data\":\"somevalue\"}";
AutomateRestResponse response = new AutomateRestResponse<JsonObject>();
response.setMessage("Success");
response.setStatus("Success");
response.setData("somevalue");
Mockito.when(
externalService.listCluster(emrInput)
).thenReturn(response);
mockMvc.perform(post("/v1/gerData"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status", is("Success")));
verify(externalService, times(1)).listCluster(emrInput);
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/v4/listCluster")
.accept(MediaType.APPLICATION_JSON).content(emrInputJosn)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
System.out.println("response body1" + result.getResponse()
.getContentAsString());
}`
Please help me with this.
It is not clear from your question what you are trying to mock.
Anyway, you should not be able to debug your service/dao that is mocked since what actually executed in the test is the mocked one and not yours.
If you want to test your controller, you can mock your service or dao and define what the response they will return, and then verify that the response you get from your controller is as you expect it to be.
#EnableWebMvc
#SpringBootApplication(scanBasePackages = { "com.yourPackage.external" })
public class YourApplication extends org.springframework.boot.web.support.SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
private static Class<Application> applicationClass = Application.class;
}
Based on what you have pasted, you can do below things:
if you are using #RunWith(SpringJUnit4ClassRunner.class) [better change to #RunWith(SpringRunner.class)] then use
#MockBean
private MyService externalService;
OR
use #RunWith(MockitoJUnitRunner.class) and
#MockBean
private MyService externalService;
#InjectMocks
private MyController controller = new MyController(externalService);
for details checkout :- testing web in spring boot
#WebAppConfiguration
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {AppplicationConfiguration.class})
public class ExternalControllerTest {
private MockMvc mockMvc;
#InjectMocks
private MyController myController ;
#MockBean
myService myService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders
.standaloneSetup(myController)
.build();
}
#Test
public void testListCluster() throws Exception {
Input emrInput = new Input();
emrInput.setId("200004773");
emrInput.setName("test");
String expected = "{\"status\":\"Success\",\"message\":\"Success\",\"data\":\"somevalue\"}";
AutomateRestResponse response = new AutomateRestResponse<JsonObject>();
response.setMessage("Success");
response.setStatus("Success");
response.setData("somevalue");
Mockito.when(
externalService.listCluster(emrInput)
).thenReturn(response);
mockMvc.perform(post("/v1/gerData"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status", is("Success")));
verify(externalService, times(1)).listCluster(emrInput);
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/v4/listCluster")
.accept(MediaType.APPLICATION_JSON).content(emrInputJosn)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
System.out.println("response body1"+ result.getResponse()
.getContentAsString());
}
}

Spring test invalid configuration

I have a simple REST controller that I'm trying to test, the test looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class ModelControllerTests {
private MediaType contentType = MediaType.APPLICATION_JSON_UTF8;
private HttpMessageConverter jacksonConverter;
private MockMvc mockMvc;
#Mock
private ModelService service;
#InjectMocks
private ModelController controller;
#Autowired
private WebApplicationContext context;
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
jacksonConverter = Arrays.stream(converters)
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull(jacksonConverter);
}
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = standaloneSetup(controller)
.build();
}
#Test
public void postModel_returnsModel() throws Exception {
when(service.doSomething(any())).thenReturn(new Model("cde", null));
mockMvc.perform(post("/model")
.content(json(new Model("abc", null)))
.contentType(contentType))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().json("{\"notEmpty\":\"abc\"}", true));
}
private String json(Object o) throws IOException {
var responseMessage = new MockHttpOutputMessage();
jacksonConverter.write(o, MediaType.APPLICATION_JSON_UTF8, responseMessage);
return responseMessage.getBodyAsString();
}
}
Now I've got a problem with dependencies and configuration, I've got the following line in my application.properties: spring.jackson.default-property-inclusion=non_null, which works fine when using the normal mockMvc (webAppContextSetup), however I wanted to mock ModelService (which is autowired in ModelController.
When using standaloneSetup to create MockMvc instance there seems to be no configuration, fields that are set to null are returned and furthermore it seems that the ModelService annotated with #Mock is not the same as the one in ModelController, therefore when postModel_returnsModel it's using the wrong service.
How can I solve this?

How can I test my SpringBoot RestController using a MockMvc when I rely on a Spring Validator?

In my rest-controller I am validating the input JSON with a custom Spring validator class.
When I now want to write unit test for the controller then I am getting the error that my Validator could not be found...
I am using constructor injecting for the two components in my rest-controller.
#Autowired
public JobController(JobValidator jobValidator, JobService jobService) {
this.jobValidator = jobValidator;
this.jobService = jobService;
}
And here my corresponding Test class.
#RunWith(SpringRunner.class)
#WebMvcTest(JobsController.class)
#AutoConfigureMockMvc
public class MailMonitorJobControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private JobService jobService;
#Test
public void givenJobs_whenGetJobs_thenReturnJsonArray() throws Exception {
Job job = new Job("TEST");
List<Job> allJobs = Arrays.asList(job);
Mockito.when(jobService.getAllJobs()).thenReturn(allJobs);
mockMvc.perform(get("/api/v1/test")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Appreciate any help, hints or suggestions!
So thanks to #pvpkiran! I had to add the JobValidator also as a Mock!
#RunWith(SpringRunner.class)
#WebMvcTest(JobsController.class)
#AutoConfigureMockMvc
public class MailMonitorJobControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private JobService jobService;
#MockBean
private JobValidator jobValidator;
#Test
public void givenJobs_whenGetJobs_thenReturnJsonArray() throws Exception {
Job job = new Job("TEST");
List<Job> allJobs = Arrays.asList(job);
Mockito.when(jobService.getAllJobs()).thenReturn(allJobs);
mockMvc.perform(get("/api/v1/test")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

Spring Controller test

I have the following:
Controller class:
#Controller
#RequestMapping("/")
public class MainController {
#Inject
#Named("dbDaoService")
IDaoService dbDaoService;
#RequestMapping(method = RequestMethod.GET)
public String init(ModelMap model) {
List<Tags> tags = dbDaoService.getAllTags();
model.addAttribute("tags", tags);
return "create";
}
}
Service class:
#Service("dbDaoService")
public class DBDaoService implements IDaoService {
#PersistenceContext(unitName = "MyEntityManager")
private EntityManager entityManager;
#Override
#Transactional
public List<Tags> getAllTags() {
if(tags == null) {
TypedQuery<Tags> query = entityManager.createNamedQuery("Tags.findAll", Tags.class);
tags = query.getResultList();
}
return tags;
}
}
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebConfig.class})
#WebAppConfiguration
public class MainControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
}
#Test
public void init() throws Exception {
Tags tag1 = new Tags("first");
Tags tag2 = new Tags("second");
DBDaoService mock = org.mockito.Mockito.mock(DBDaoService.class);
when(mock.getAllTags()).thenReturn(Arrays.asList(tag1, tag2));
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("create"))
.andExpect(forwardedUrl("/WEB-INF/views/create.jsp"))
.andExpect(model().attribute("tags", hasItem(
allOf(
hasProperty("name", is("first"))
)
)))
.andExpect(model().attribute("tags", hasItem(
allOf(
hasProperty("name", is("second"))
)
)));
}
}
When I run MainControllerTest, it fails, because it gets "Tag" entities from DBDaoService (it means, from database), but I expect it will use mock service.
What's wrong?
The current test setup loads the spring configuration and uses the WebApplicationContext to handle request due to MockMvcBuilders.webAppContextSetup(context).build();. Thus mocked beans are ignored since they are not instructed to participate.
To interweave them the configuration should be updated to MockMvcBuilders.standaloneSetup(controller).build() instead.
The sample test case should look like below (note the additional mock for MainController and configuration update to MockMvcBuilders.standaloneSetup along with instruction to use Mockito annotation via MockitoAnnotations.initMocks)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebConfig.class})
#WebAppConfiguration
public class MainControllerTest {
private MockMvc mockMvc;
#InjectMocks
private MainController controller;
#Mock
private DBDaoService daoService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setViewResolvers(viewResolver)
.build();
Tags tag1 = new Tags("first");
Tags tag2 = new Tags("second");
when(daoService.getAllTags()).thenReturn(Arrays.asList(tag1, tag2));
}
#Test
public void init() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("create"))
.andExpect(forwardedUrl("/WEB-INF/views/create.jsp"))
.andExpect(model().attribute("tags", hasItem(
allOf(
hasProperty("name", is("first"))
)
)))
.andExpect(model().attribute("tags", hasItem(
allOf(
hasProperty("name", is("second"))
)
)));
}
}

Resources