Can't mock repository when testing with mockmvc - java

I have this quite simple controller class and a simple (jpa) repository.
What I want to do is to test it's api but mock it's repository and let it return an object or not depending on the test case.
My problem now is that I don't know how to do that.
I know how to mock a repository and inject it to a controller/service class with #Mock / #InjectMocks / when().return()
But I fail when I want to do the same after doing a request with MockMvc.
Any help is highly appreciated
The controller
import java.util.Optional;
#RestController
#Slf4j
public class ReceiptController implements ReceiptsApi {
#Autowired
private ReceiptRepository receiptRepository;
#Autowired
private ErrorResponseExceptionFactory exceptionFactory;
#Autowired
private ApiErrorResponseFactory errorResponseFactory;
#Override
public Receipt getReceipt(Long id) {
Optional<ReceiptEntity> result = receiptRepository.findById(id);
if (result.isEmpty()) {
throw invalid("id");
}
ReceiptEntity receipt = result.get();
return Receipt.builder().id(receipt.getId()).purchaseId(receipt.getPurchaseId()).payload(receipt.getHtml()).build();
}
private ErrorResponseException invalid(String paramName) {
return exceptionFactory.errorResponse(
errorResponseFactory.create(HttpStatus.NOT_FOUND.value(), "NOT_VALID", String.format("receipt with id %s not found.", paramName))
);
}
}
And it's test class
#WebMvcTest(ReceiptController.class)
#RestClientTest
public class ReceiptControllerTest {
#InjectMocks
private ReceiptController receiptController;
#Mock
private ReceiptRepository receiptRepository;
#Mock
private ErrorResponseExceptionFactory exceptionFactory;
#Mock
private ApiErrorResponseFactory errorResponseFactory;
private MockMvc mvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(
new ReceiptController())
.build();
}
#Test
public void getReceiptNotFoundByRequest() throws Exception {
mvc.perform(MockMvcRequestBuilders
.get("/receipt/1")
.header("host", "localhost:1348")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
//TODO: Finish this test
#Test
public void getReceiptFoundByRequest() throws Exception {
ReceiptEntity receipt1 = ReceiptEntity.builder().id(99999L).purchaseId(432L).html("<html>").build();
when(receiptRepository.findById(1L)).thenReturn(Optional.of(ReceiptEntity.builder().id(1L).purchaseId(42L).html("<html></html>").build()));
ResultActions result = mvc.perform(get("/receipt/1")
.header("host", "localhost:1348")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

Within your setUp() method, you're using Mockito to mock the beans annotated with #Mock and inject them in a new instance of ReceiptController, which is then put into the field annotated with #InjectMocks.
On the next line, you're setting up MockMvc with a new instance of ReceiptController (you use new ReceiptController()), rather than using the instance that Mockito created. That means that all fields within that instance will be null.
This pretty much boils down exactly to Why is my Spring #Autowired field null.
To solve this, you could pass receiptController to MockMvc. In that case, you could also remove the #WebMvcTest and #RestClientTest as you're not using them.
Alternatively, you could setup your test with #RunWith(SpringRunner.class), and properly set up a Spring test by using #Autowired in stead of #InjectMocks and #MockBean in stead of #Mock. In that case, you don't need a setUp() method at all, as MockMvc could be autowired by Spring.

#WebMvcTest and MockMvc allows you to do integration testing, not unit testing.
They allow you to boot an actual Spring application (using a random port) and test the actual controller class with its dependencies. This means that the variables you declared at the top of your test are not actually used in your current setup.
If you wish to unit-test your controller, you can remove #WebMvcTest, create mock dependencies (like you did) and call your methods directly instead of using MockMvc.
If you really wish to write an integration test, you need to mock your external dependencies (the database for example). You can configure spring to use an embedded database (H2 for example) in the test environment, so that you do not affect your real database.
See an example here : https://www.baeldung.com/spring-testing-separate-data-source

Related

Spring Boot unit test constructor injection

I'm using Spring Boot to create a REST API and write some unit tests on my controllers.
I know that the recommended manner to inject beans in spring is the constructor injection.
But when i add the #SpringBootTest annotation to my test class, I can not inject my controller class with constructor, I find myself obliged to use #Autowired.
Have some explanation and is there another way to use constructor injection with SpringBootTest.
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class PersonControllerTest {
#LocalServerPort
private int port;
#Autowired
private PersonController controller;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/cvtech/Persons/",
String.class)).contains("content");
}
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
#Test
void findAllByJob() {
}
}
For those using Kotlin, using field-injection means having to use lateinit var fields. Which is far from ideal.
It is possible to use constructor injection on SpringBoot tests however, using the #TestConstructor:
#ExtendWith(SpringExtension::class)
#TestConstructor(autowireMode = ALL)
#SpringBootTest(
classes = [MyApplication::class],
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
)
internal class MyIntegrationTest(
val beanA: BeanA,
#Qualifier("some qualified") val beanB: BeanB,
) {
...
// Tests can use the beans injected in the constructor without any problems
...
}
It's fine for your test to use field injection as the Test itself is not part of your domain; the test won't be part of your application context.
Also
You don't want to use SpringBootTest to test a controller, because that will wire ALL beans which can be way too heavy and time-consuming. Instead, you probably only want to create your controller and it's dependencies.
So your best option is to use #WebMvcTest which will only create the beans required for testing the specified controller.
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = PersonController.class)
class PersonControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
mockMvc.perform(get("/cvtech/Persons"))
.andExpect(status().isOk())
.andExpect(content().string(contains("content")));
}
}
Note that #WebMvcTest will search for a class annotated with #SpringBootConfiguration as it's default configuration. If it does not find it, or you want to manually specify some configuration classes, also annotate the test with #ContextConfiguration.
Also, as a sidenote, when using TestRestTemplate, you don't need to specify host and port. Just call restTemplate.getForObject("/cvtech/persons", String.class));
Same when using MockMvc or WebTestClient.

#SpringBootTest: #MockBean not injected when multiple test classes

I want to write controller tests that also test my annotations. What I've read so far is that RestAssured one of the ways to go.
It works smoothly when I only have one controller test in place. However, when having 2 or more controller test classes in place, the #MockBeans seem to not be used properly.
Depending on the test execution order, all tests from the first test class succeed, and all others fail.
In the following test run, the PotatoControllerTest was executed first, and then the FooControllerTest.
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles({"test", "httptest"})
class FooControllerTest {
#MockBean
protected FooService mockFooService;
#MockBean
protected BarService mockBarService;
#LocalServerPort
protected int port;
#BeforeEach
public void setup() {
RestAssured.port = port;
RestAssured.authentication = basic(TestSecurityConfiguration.ADMIN_USERNAME, TestSecurityConfiguration.ADMIN_PASSWORD);
RestAssured.requestSpecification = new RequestSpecBuilder()
.setContentType(ContentType.JSON)
.setAccept(ContentType.JSON)
.build();
}
#SneakyThrows
#Test
void deleteFooNotExists() {
final Foo foo = TestUtils.generateTestFoo();
Mockito.doThrow(new DoesNotExistException("missing")).when(mockFooService).delete(foo.getId(), foo.getVersion());
RestAssured.given()
.when().delete("/v1/foo/{id}/{version}", foo.getId(), foo.getVersion())
.then()
.statusCode(HttpStatus.NOT_FOUND.value());
Mockito.verify(mockFooService, times(1)).delete(foo.getId(), foo.getVersion());
}
...
}
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles({"test", "httptest"})
class PotatoControllerTest {
#MockBean
protected PotatoService mockPotatoService;
#LocalServerPort
protected int port;
#BeforeEach
public void setup() {
RestAssured.port = port;
RestAssured.authentication = basic(TestSecurityConfiguration.ADMIN_USERNAME, TestSecurityConfiguration.ADMIN_PASSWORD);
RestAssured.requestSpecification = new RequestSpecBuilder()
.setContentType(ContentType.JSON)
.setAccept(ContentType.JSON)
.build();
}
...
}
Wanted but not invoked:
fooService bean.delete(
"10e76ae4-ec1b-49ce-b162-8a5c587de2a8",
"06db13f1-c4cd-435d-9693-b94c26503d40"
);
-> at com.xxx.service.FooService.delete(FooService.java:197)
Actually, there were zero interactions with this mock.
I tried to fix it with a common ControllerTestBase which configures all mocks and all other controller tests extending the base class. Which worked fine on my machine, but e.g. not in the pipeline. So I guess it is not really stable.
Why is Spring not reloading the context with the mocks? Is this the "best" way of testing my controllers?
It would be much easier and way faster to just use MockMvc.
You can just create a standalone setup for your desired controller and do additional configuration (like setting exception resolvers). Also you're able to inject your mocks easily:
#Before
public void init() {
MyController myController = new MyController(mock1, mock2, ...);
MockMvc mockMvc =
MockMvcBuilders.standaloneSetup(myController)
.setHandlerExceptionResolvers(...)
.build();
}
Afterwards you can easily call your endpoints:
MvcResult result = mockMvc.perform(
get("/someApi"))
.andExpect(status().isOk)
.andReturn();
Additional validation on the response can be done like you already know it.
Edit: As a side note - this is designed to explicitly test your web layer. If you want to go for some kind of integration test going further down in your application stack, also covering business logic, this is not the right approach.

Nullpointer when using Mockito for unit tests

I am creating a spring boot API for my application. I am attempting to unit test my service implementation using mockito to mock out the detail. The service will add a new building entity to the database. Below is the Service implementation and the test implementation.
Building Service:
#Slf4j
#Service
public class BuildingServiceImpl implements BuildingService {
private BuildingRepository buildingRepository;
private BuildingRequestToEntityMapper buildingRequestToEntityMapper;
public BuildingServiceImpl(BuildingRepository buildingRepository, BuildingRequestToEntityMapper
buildingRequestToEntityMapper){
this.buildingRepository=buildingRepository;
this.buildingRequestToEntityMapper=buildingRequestToEntityMapper;
}
public HttpStatus addBuilding(BuildingRequest buildingRequest){
log.info("Inside addBuilding() service");
BuildingEntity buildingEntity = buildingRequestToEntityMapper.map(buildingRequest);
buildingRepository.save(buildingEntity);
log.info("Building saved "+ buildingEntity);
return HttpStatus.CREATED;
}
BuildingServiceImpl_UT
#RunWith(MockitoJUnitRunner.class)
public class BuildingServiceImpl_UT {
#Mock
BuildingRequestToEntityMapper buildingRequestToEntityMapper;
#Mock
BuildingRepository buildingRepository;
#InjectMocks
BuildingServiceImpl buildingServiceImpl;
#Test
public void buildingService_MapsRequest_AndSaveEntity(){
BuildingRequest buildingRequest = BuildingRequest.builder()
.name("TestName")
.telephone("4444444444")
.postcode("TEst")
.address("testAddress").build();
when(buildingServiceImpl.addBuilding(any(BuildingRequest.class))).thenReturn(HttpStatus.CREATED);
when(buildingRepository.save(any(BuildingEntity.class))).thenReturn(new BuildingEntity());
buildingServiceImpl.addBuilding(buildingRequest);
verify(buildingRepository, times(1)).save(any());
}
I have mocked the mapper and repository and injected them into the service, but when i run the test I get a null pointer exception at the first when().thenReturn() statement in the test class. Any help please. Thanks
I don't understand your first when().thenReturn()! You try to do this on the buildingServiceImpl wich is not a mock! Further more this makes no sense because you want to test this methode!
I think you should define a when().thenReturn() for the mock buildingRequestToEntityMapper, but in your implementation you don't need to define a return for buildingRequestToEntityMapper.map(). In this case the variable buildingEntity will have the value null which should work in your test case.
#RunWith(MockitoJUnitRunner.class)
public class BuildingServiceImpl_UT {
#Mock
BuildingRequestToEntityMapper buildingRequestToEntityMapper;
#Mock
BuildingRepository buildingRepository;
#InjectMocks
BuildingServiceImpl buildingServiceImpl;
#Test
public void buildingService_MapsRequest_AndSaveEntity(){
BuildingRequest buildingRequest = BuildingRequest.builder()
.name("TestName")
.telephone("4444444444")
.postcode("TEst")
.address("testAddress").build();
when(buildingRepository.save(any(BuildingEntity.class))).thenReturn(new BuildingEntity());
buildingServiceImpl.addBuilding(buildingRequest);
verify(buildingRepository, times(1)).save(any());
verify(buildingRequestToEntityMapper).map(any());
}
This:
when(buildingServiceImpl.addBuilding(any(BuildingRequest.class))).thenReturn(HttpStatus.CREATED);
is not necessary, you want to test method: addBuilding not mock it.

How do I know if Mock in Spring boot is being used or not?

I am testing a service class which uses a Dao layer under it.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class AppServiceTest {
#Autowired
#InjectMocks
private AppService appService;
private AppConfig appConfig = new AppConfig(), appConfigOut = new AppConfig();
#MockBean //This statement is under inspection in the problem
private AppDao appDao;
#Before
public void setUp() throws Exception {
String appKey = "jsadf87bdfys78fsd6f0s7f8as6sd";
appConfig.setAppKey(appKey);
appConfigOut.setAppKey(appKey);
appConfigOut.setRequestPerMinute(null);
appConfigOut.setRequestDate(DateTime.now());
MockitoAnnotations.initMocks(this);
}
#Test
public void testFetchAppConfigValidParam() throws Exception {
when(appDao.fetchAppConfig(appConfig)).thenReturn(appConfigOut);
assertThat(appService.fetchAppConfig(appConfig)).isEqualToComparingFieldByField(appConfigOut);
}
In the above program when I write #MockBean, the test throws a NullPointerException, but when I write #Mock the test executes successfully. I think the appDao being called is the actual one defined in appService and accessing the database. This is because the time taken by the test is around 200ms and usual test cases for other applications is 60ms-100ms. But I am not sure because other cases where DAO really access data takes 400ms to 500ms.
How do I know mock is actually working and when appService calls the appDao method from inside it is actually the mock. Is there any programmatical way to verify this.
P.S. If #Mock works in this scenario what is #MockBean is useful for in spring boot.
M.Deinum is pointing you in the correct direction in the comment.
Maybe you want to give the spring documentation about Mocking and Spying in tests a read - https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-mocking-beans
But to answer you question - you can use MockingDetails to tell if an object is a mock.
MockingDetails mockingDetails = org.mockito.Mockito.mockingDetails(appDao)
boolean appDaoIsMock = mockingDetails.isMock()
(https://stackoverflow.com/a/15138628/5371736)

Mockito when() not working

I have created a test case using Mockito.I want to test a controller.When the controller is called i want to send back a responce object and dont want the code inside controller to execute.But even though i have used when(functionName).thenReturn(), its getting into the controllers code.What am i doing wrong here?
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class MockitoController {
#Autowired
private WebApplicationContext webApplicationContext;
protected MockMvc mockMvc;
#SuppressWarnings("unchecked")
#Before
public void setup() {
MyController myController = Mockito.mock(myController.class);
ResponseView jsonResponse = new ResponseView();
jsonResponse.setStatus(1);
jsonResponse.setMessage("true");
Mockito.when((myController.deleteMedia(Mockito.anyInt()))).thenReturn(jsonResponse);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
}
#Test
public void deleteMediaMockito() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders.delete("/library/99")
.accept(MediaType.APPLICATION_JSON);
MvcResult result = this.mockMvc.perform(requestBuilder).andReturn();
JSONObject jsonObject = new JSONObject(result.getResponse().getContentAsString());
assertEquals(1, jsonObject.get("status"));
}
}
I would try a local class containing your mock controller. Something like this inside your test class. This will register your mock and overwrite the real MyController inside the application context.
#Configuration
public static class MyMockConfig {
#Bean
#Primary
MyController myController() {
Mockito.mock(MyController.class);
}
}
But I am not convinced that the thing you are testing makes sense. MockMvc is used to test your controller as a whole including the requestMappings etc.
So why would you mock parts of it. The controller is your unit under test. Try to mock away its dependencies.
But still - the code I posted can help to inject mocks into spring beans.
Try changing the Mockito.anyInt() method for a constant here:
Mockito.when((myController.deleteMedia(Mockito.anyInt()))).thenReturn(jsonResponse);
This method should be used for matching purposes, not to provide values. I had a situation similar to yours, the tests/whens were behaving really strange, and began doing what I was expecting to only after I replace them for constants.
I hope it helps!

Categories

Resources