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!
Related
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.
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
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)
I have a class which contains a few service activator methods as follows:
#MessageEndpoint
public class TestService {
#ServiceActivator
public void setComplete(Message<String> message){
//do stuff
}
}
In the integration flow, one of the channels call one of these methods:
#Bean
public TestService testService() {
return new TestService();
}
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.handle("testService", "setComplete")
.handle(logger())
.get();
}
I'm writing a unit test for this flow and using Mockito for mcoking the service activator class:
#ContextConfiguration(classes = IntegrationConfig.class)
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class AppTest {
#Mock
private TheGateway startGateway;
#Mock
private TestService testrvice;
#Autowired
#Qualifier("testChannel")
DirectChannel testChannel;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test()
public void testMessageProducerFlow() throws Exception {
Mockito.doNothing().when(startGateway).execute("test");
startGateway.execute("test");
Mockito.verify(startGateway).execute("test");
TestChannel.send(new GenericMessage<>("test"));
Mockito.verify(testService).setComplete(new GenericMessage<>("test"));
}
}
When I don't mock the TestService, it executes the flow without issues.
Any guideance on how to Mock the Service activator class would be helpful.
UPDATE:
When I mock it (as shown in snippet above), it does not call the mocked object, instead executes the actual stuff, and the last line Mockito.verify(testService)... asserts that the mock testService was never called.
First of all you misunderstood how Spring Test Framework works.
#ContextConfiguration(classes = IntegrationConfig.class) loads the config as is without any modification and start an application context based on that config.
According to the first condition your .handle("testService", "setComplete") uses testService() #Bean not #Mock
Only after the test applicationContext startup all those #Mocks and #Autowireds start working.
In other words your mocking doesn't change anything in the original IntegrationConfig.
In the Framework with use reflection to retrieve some field of the particular bean to replace it with the mock. But it isn't so easy way.
I suggest you to distinguish the Integration and Service configuration and use two different classes for production and for testing. Something like this:
The testService() #Bean must be moved from the IntegrationConfig to the new #Configuration class for production.
The TestServiceConfig may look like this:
#Bean
public TestService testService() {
return Mockito.mock(new TestService());
}
And finally your AppTest should be modified like this:
#ContextConfiguration(classes = {IntegrationConfig.class, TestServiceConfig.class})
....
#Autowired
private TestService testrvice;
That's everything is just because the application context and unit test scopes are on the different levels.
I hace a Web application working good. Now I'm trying to write unit test for it. My webapp has the following conversionService
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="....Class1ToStringConverter"/>
<bean class="....StringToClass1Converter"/>
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService" />
Which works nice and when I do a request to
/somepath/{class1-object-string-representation}/xx
everything works as expected (string got interpreted as Class1 object).
My problem is trying to write a unit test to my controller. The conversionService is just not used and spring just tell me
Cannot convert value of type [java.lang.String] to required type [Class1]: no matching editors or conversion strategy found
My Test so far:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/applicationContext.xml", "file:src/main/webapp/WEB-INF/jpm-servlet.xml"})
#WebAppConfiguration()
public class GeneralTest {
#Autowired
private WebApplicationContext ctx;
private MockMvc mockMvc;
private TestDAO testDAO = org.mockito.Mockito.mock(TestDAO.class);
#Before
public void setUp() throws Exception {
Mockito.reset(testDAO);
mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
#Test
public void testList() throws Exception {
final Test first = new Test(1L, "Hi", 10, new Date(), true);
final Test second = new Test(2L, "Bye", 50, new Date(), false);
first.setTest(second);
when(testDAO.list()).thenReturn(Arrays.asList(first, second));
mockMvc.perform(get("/jpm/class1-id1"))
.andExpect(status().isOk())
.andExpect(view().name("list"))
.andExpect(forwardedUrl("/WEB-INF/jsp/list.jsp"));
}
What Im missing? Thanks
Mock Converter Like this,
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(new StringToClass1Converter());
Deencapsulation.setField(FIXTURE, conversionService);
This post is a little outdated, but still one of the first Google results, when searching for this problem.
So here is an Update for Spring Framework 5.
When you do configure a WebMvc context like documented in the Spring Documentation, you write something like this:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
However, this is not loaded in a JUnit context! God knows why. But you need to declare your configuration class like this:
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
#Override
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
So remove the #EnableWebMvc annotation and extend from another class as described above. You likely don't have to change anything else.
Then your addFormatters method is also called in your unit tests.
This is very unintuitive and odd. But it's true. You can debug the spring-test source code with breakpoints and see, that all the other methods of the WebMvcConfigurer interface are called, but addFormatters is not. Maybe they have just forgotten to call it or they have other "reasons". By extending from WebMvcConfigurationSupport every method is called as desired and your JUnit tests succeed.
In particular, this code gets eventually executed:
#Bean
public FormattingConversionService mvcConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
Honestly, I do not know what exactly fails, when just implementing the interface as described in the Spring documentation.
I had the same issue. If you are trying to test a single controller, you could try the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/applicationContext.xml", "file:src/main/webapp/WEB-INF/jpm-servlet.xml"})
#WebAppConfiguration()
public class GeneralTest {
#Autowired
private WebApplicationContext ctx;
#Autowired
private FormattingConversionServiceFactoryBean conversionService;
private MockMvc mockMvc;
#Mock
private TestDAO testDAO;
/* The following assumes you are injecting your DAO into your controller
* If you are using a service layer (most likely), you should
* inject your DAO into your service and your service into your controller.
*/
#InjectMocks
private YourControllerClass controllerToTest;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
//get the conversion service from the factory bean
FormattingConversionService cs = conversionService.getObject();
Mockito.reset(testDAO);
//setup MockMvc using the conversion service
mockMvc = MockMvcBuilders.standaloneSetup(controllerToTest)
.setConversionService(cs)
.build();
}
#Test
public void testList() throws Exception {
final Test first = new Test(1L, "Hi", 10, new Date(), true);
final Test second = new Test(2L, "Bye", 50, new Date(), false);
first.setTest(second);
when(testDAO.list()).thenReturn(Arrays.asList(first, second));
mockMvc.perform(get("/jpm/class1-id1"))
.andExpect(status().isOk())
.andExpect(view().name("list"))
.andExpect(forwardedUrl("/WEB-INF/jsp/list.jsp"));
}
Hope that helps!
I realize that this is an old thread but like me, there'll be others that're going to stumble across this in the future after having spent several hours troubleshooting why their custom converter does not get invoked.
The solution proposed by #Mariano D'Ascanio isn't adequate in cases where there're simply no controllers, at least not the ones that you wrote, like when you're using Spring JPA. MockMvcBuilders.standaloneSetup requires at least one controller to be passed to the constructor so you can't use that for such cases. The way to get around that is by injecting the org.springframework.core.convert.converter.ConverterRegistry or better yet, it's subclass org.springframework.core.convert.converter.FormatterRegistry and then in a #PostContruct method, register your custom converter/formatter as shown below:
#PostConstruct
void init() {
formatterRegistry.removeConvertible(String.class, OffsetDateTime.class);
formatterRegistry.addFormatter(customOffsetDateTimeFormatter);
formatterRegistry.addConverter(customOffsetDateTimeConverter);
}
The trick is to inject the ConverterRegistry using a name, not a type, because for web tests, there're two converter registries, default and mvc. Spring tests use the default converter, so that's the one you need.
// GOTCHA ALERT: There's also a mvcConversionService; tests DO NOT use that
#Resource(name = "defaultConversionService")
private FormatterRegistry formatterRegistry;
Hope this helps.