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.
Related
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
Assume, that I have a test configuration with several Spring beans, that are actually mocked and I want to specify the behavior of those mocks inside JUnit test suite.
#Profile("TestProfile")
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {
"some.cool.package.*"})
public class IntegrationTestConfiguration {
#Bean
#Primary
public Cool cool() {
return Mockito.mock(Cool.class);
}
}
// ...
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#ActiveProfiles("TestProfile")
public class CoolIntegrationTest {
private final Cool cool;
#Autowired
public CoolIntegrationTest(Cool cool) {
this.cool = cool;
}
#Test
public void testCoolBehavior {
when(cool.calculateSomeCoolStuff()).thenReturn(42);
// etc
}
}
If I run this test I will get:
java.lang.Exception: Test class should have exactly one public zero-argument constructor
I know the workaround like use Autowired fields in tests, but I wonder if there a way to use Autowired annotation in JUnit tests?
It's not the autowiring that's the problem, it's the no-arg constructor. JUnit test classes should have a single, no argument constructor. To achieve what you are attempting to do, you should do the following:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#ActiveProfiles("TestProfile")
#ContextConfiguration(classes = {IntegrationTestConfiguration.class})
public class CoolIntegrationTest {
#Autowired
private final Cool cool;
#Test
public void testCoolBehavior {
when(cool.calculateSomeCoolStuff()).thenReturn(42);
// etc
}
}
The contextConfiguration annotation tells spring which config to use for the test, and autowiring the field instead of the constructor will allow you to test your spring bean.
To run a test using Spring you have to add #RunWith(SpringRunner.class) and make sure that your class is added to the classpath. There are a few ways to do it. I.e.
Add class to MVC configuration #WebMvcTest({Class1.class, Class2.class}) or use #ContextConfiguration.
But I see your code, I suppose that it would be easier just use #Mock or #MockBean to mock your beans. It will be much easier.
JUnit requires the Test case to have a no-arg constructor, so, since you don't have one, the exception happens before the wiring process.
So Constructor-Autowiring just doesn't work in this case.
So what to do?
There are many approaches:
The easiest one (since you have spring) is taking advantage of #MockBean annotation:
#RunWith(SpringRunner.class)
#SpringBootTest
....
class MyTest {
#MockBean
private Cool cool;
#Test
void testMe() {
assert(cool!= null); // its a mock actually
}
}
Besides args constructor you need to have additional one no-args constructor. Try add it and check if this exception still occurcs.
#Autowired
public CoolIntegrationTest(Cool cool) {
this.cool = cool;
}
public CoolIntegrationTest() {}
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!
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 am attempting to use mocks in my integration test and am not having much luck. I am using Spring 3.1.1 and Mockito 1.9.0, and the situation is as follows:
#Component
public class ClassToTest {
#Resource
private Dependency dependency;
}
and
#Component
public class Dependency {
#Resource
private NestedDependency nestedDependency;
}
Now, I want to do an integration test of ClassToTest using Spring's JavaConfig. This is what I have attempted, and it doesn't work:
#Test
#ContextConfiguration
public class ClassToTestIntegrationTest {
#Resource
private ClassToTest classToTest;
#Resource
private Dependency mockDependency;
#Test
public void someTest() {
verify(mockDependency).doStuff();
// other Mockito magic...
}
#Configuration
static class Config {
#Bean
public ClassToTest classToTest() {
return new ClassToTest();
}
#Bean
public Dependency dependency() {
return Mockito.mock(Dependency.class);
}
}
}
I have simplified my setup to make the question easier to understand. In reality I have more dependencies and only want to mock some of them - the others are real, based on config imported from my prod #Configuration classes.
What ends up happening is I get a NoSuchBeanDefinitionException saying that there are no beans of type NestedDependency in the application context. I don't understand this - I thought Spring would receive Mockito's mocked instance of Dependency and not even look at autowiring it. Since this isn't working I end up having to mock my entire object graph - which completely defeats the point of mocking!
Thanks in advance for any help!
I had the same problem and I found another solution.
When Spring instantiate all your beans, it will check if it's a Mockito Mock and in this case, I return false for injection property. To use it, just inject it in a Spring context
Code below:
public class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
private static final MockUtil mockUtil = new MockUtil();
public MockBeanFactory() {
super();
}
#Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return !mockUtil.isMock(bean);
}
}
What Mockito does when mocking classes is it creates a subclass using cglib having some fancy name like: Dependency$EnhancerByMockito (IIRC). As you probably know, subclasses inherit fields from their parent:
#Component
public class Dependency {
#Resource
private NestedDependency nestedDependency;
}
public class Dependency$EnhancerByMockito extends Dependency{
//...
}
This means Spring still sees the field in base class when presented with mock. What you can do:
Use interfaces, which will cause Mockito to employ dynamic proxies rather than CGLIB-generated classes
Mock NestedDependency - I know it will just cascade the problem one level further
Disable #Resource annotation scanning for tests