How to cleanly test Spring Controllers that retrieve parameters with DomainClassConverter? - java

I am big on clean well-isolated unit tests. But I am stumbling on the "clean" part here for testings a controller that uses DomainClassConverter feature to get entities as parameters for its mapped methods.
#Entity
class MyEntity {
#Id
private Integer id;
// rest of properties goes here.
}
The controller is defined like this
#RequestMapping("/api/v1/myentities")
class MyEntitiesController {
#Autowired
private DoSomethingService aService;
#PostMapping("/{id}")
public ResponseEntity<MyEntity> update(#PathVariable("id")Optional<MyEntity> myEntity) {
// do what is needed here
}
}
So from the DomainClassConverter small documentation I know that it uses CrudRepository#findById to find entities. What I would like to know is how can I mock that cleanly in a test.
I have had some success by doing this steps:
Create a custom Converter/Formatter that I can mock
Instantiate my own MockMvc with above converter
reset mock and change behaviour at each test.
The problem is that the setup code is complex and thus hard to debug and explain (my team is 99% junior guys coming from rails or uni so we have to keep things simple). I was wondering if there is a way to inject the desired MyEntity instances from my unit test while keep on testing using the #Autowired MockMvc.
Currently I am trying to see if I can inject a mock of the CrudRepository for MyEntity but no success. I have not worked in Spring/Java in a few years (4) so my knowledge of the tools available might not be up to date.

So from the DomainClassConverter small documentation I know that it uses CrudRepository#findById to find entities. What I would like to know is how can I mock that cleanly in a test.
You will need to mock 2 methods that are called prior the CrudRepository#findById in order to return the entity you want. The example below is using RestAssuredMockMvc, but you can do the same thing with MockMvc if you inject the WebApplicationContext as well.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = SomeApplication.class)
public class SomeControllerTest {
#Autowired
private WebApplicationContext context;
#MockBean(name = "mvcConversionService")
private WebConversionService webConversionService;
#Before
public void setup() {
RestAssuredMockMvc.webAppContextSetup(context);
SomeEntity someEntity = new SomeEntity();
when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
.thenReturn(true);
when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
.thenReturn(someEntity);
}
}
At some point Spring Boot will execute the WebConversionService::convert, which will later call DomainClassConverter::convert and then something like invoker.invokeFindById, which will use the entity repository to find the entity.
So why mock WebConversionService instead of DomainClassConverter? Because DomainClassConverter is instantiated during application startup without injection:
DomainClassConverter<FormattingConversionService> converter =
new DomainClassConverter<>(conversionService);
Meanwhile, WebConversionService is a bean which will allow us to mock it:
#Bean
#Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
It is important to name the mock bean as mvcConversionService, otherwise it won't replace the original bean.
Regarding the stubs, you will need to mock 2 methods. First you must tell that your mock can convert anything:
when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
.thenReturn(true);
And then the main method, which will match the desired entity ID defined in the URL path:
when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
.thenReturn(someEntity);
So far so good. But wouldn't be better to match the destination type as well? Something like eq(TypeDescriptor.valueOf(SomeEntity.class))? It would, but this creates a new instance of a TypeDescriptor, which will not match when this stub is called during the domain conversion.
This was the cleanest solution I've put to work, but I know that it could be a lot better if Spring would allow it.

Related

Generating unit tests for my service implementations on the spring boot application

after hours of tries and fails I come to you in hope of a solution.
I'm struggle making unit tests for my spring boot application. I'm using mockito and Junit 5.
My architecture is made out like this:
A controller
An interface of the service
A implementation of the service interface
A repository extending CrudRepository<Entity, Long>
For now I just want to test out my service implementation.
This is how it looks like for now :
`
#SpringBootTest public class ServiceImplTest{
#Mock
private Entity e;
#MockBean
private EntityRepository entityRepository;
#MockBean
private EntityService entityService;
#BeforeEach
init(){
e = new Entity();
e.name ="abc";
}
#Test
private simpleTest(){
// saving my element in the mocked repository
entityRepository.save(e);
// I have a repository query to delete an element in a specific way. I ask it to return 1 if it receives the order to activate this method
doReturn(1).when(entityRepository).specialDeleteEntity(1L);
// in the code serviceDeleteEntity() does some operations then calls entityRepository.specialDeleteEntity
int howMany = entityService.serviceDeleteEntity(1L);
// this fails because there was nothing in the repository to be deleted
assertEquals(howMany, 1);
}
}
I just have a feeling the the Mocked Repository is not connected to my Mocked Service and by this, the operations between them don't work.
I have also tried another solution where I didn't mock the repository , just in case :
#SpringBootTest class ServiceImplTest {
#MockBean
private EntityRepository mockEntityRepository;
#Autowired
private EntityService entityService;
#Test
void testDelete() {
// Given
final Entity entity = new Entity();
entity.name = "abc";
// Setup
when(mockEntityRepository.specialDeleteEntity(1L)).thenReturn(1);
// When
final int result = entityService.specialDeleteEntity(1L);
// Then
assertThat(result).isEqualTo(1);
verify(mockEntityRepository).specialDeleteEntity(1L);
}
}
I may lack some anotations or some methods maybe. I just want your advice on the problem and maybe a step towards the solution. Thank you very much.
There are a few issues with your test:
1. Wrong use of mocks
A mock is a dummy implementation of a class. You use them to bypass the normal behaviour of those classes. For example, if you write a unit test for EntityService, you don't want to set up an entire database and insert/delete mock data. So in that case you want to mock dependencies such as EntityRepository.
This means that in your EntityServiceTest you should only be mocking EntityRepository. You shouldn't mock Entity because this class normally doesn't contain a lot of behaviour and you shouldn't mock EntityService because you want to test the actual behaviour of this service and not of a mock.
2. Choose one mocking framework
Your test is currently using a combination of #Mock and #MockBean.
Both of these annotations allow you to mock a class.
The difference is that #Mock relies only on Mockito and #MockBean mocks the class and creates a Spring bean of it to use for autowiring.
This means that the #MockBean annotation only works when you run (a part of) your Spring Boot application.
This is why you use the #SpringBootTest annotation.
The downside is that this might require additional configuration and slows down the tests due to starting/stopping the application.
If you only need to unit test a single class with some mocks, it would be easier to write a test with Mockito only.
To do so, use the #Mock annotation and replace #SpringBootTest with #ExtendWith(MockitoExtension.class):
// Replace #SpringBootTest with #ExtendWith to enable testing with Mockito
#ExtendWith(MockitoExtension.class)
class EntityServiceTest {
// Add #InjectMocks to the class where the mocks should be injected into.
// Normally this is the class that you want to test
#InjectMocks
private EntityService service;
// Use #Mock in stead of #MockBean
#Mock
private EntityRepository repository;
// ...
}
3. Test the behaviour of your class
As I mentioned before, a mock is a dummy implementation of a class.
So that means that if you call repository.save(..), it doesn't really do anything as there's no behaviour and no database behind it.
What you actually want to test is whether the service. serviceDeleteEntity() method calls the repository. specialDeleteEntity() method with the right arguments.
This means that your second example of your test is the right way.
The only thing you don't need is the entity because your test doesn't rely on it (assuming that your service passes the id argument to the repository and returns the result of the query):
#Test
void testDelete() {
// Given
when(mockEntityRepository.specialDeleteEntity(1L)).thenReturn(1);
// When
final int result = entityService.specialDeleteEntity(1L);
// Then
assertThat(result).isEqualTo(1);
verify(mockEntityRepository).specialDeleteEntity(1L);
}
thanks to #g00glen00b for the solution.
#ExtendWith(MockitoExtension.class)
public class EntityServiceImplTest {
#InjectMocks
private EntityServiceImpl entityServiceImpl;
#Mock
private EntityRepository entityRepository;
#Test{
Entity e = new Entity();
when(entityRepository.getEntityById(1L)).thenReturn(e);
final Entity result = entityServiceImpl.getEntityById(1L);
assertEquals(e, result);
}
}
The issue here was that i had to use MockitoExtension on one hand.
Another problem was that, in my service implementation method , there was a hidden "new Timestamp(System.currentTimeMillis())" used to call the method in the repository.
So there was always a difference between the time I "promised" and the time created during the call of the service implementation.
Thank you again
I think you have to insert the mocked repository into your service in order it to be used:
entityService.setRepository(mockEntityRepository);

How to mock a class created in constructor?

Imagine I have a bean JdbcTemplate, and in only once class I need NamedParameterJdbcTemplate explicit. It is created inside the constructor.
Question: how can I mock it during junit tests?
#Service
public class QueryService {
private final NamedParameterJdbcTemplate namedJdbc;
public BookingExportService(JdbcTemplate jdbc) {
this.namedJdbc = new NamedParameterJdbcTemplate(jdbc);
}
public void sql() {
namedJdbc.query(sql1, mapSqlParameterSource, resultSetExtractor);
namedJdbc.query(sql2, mapSqlParameterSource, resultSetExtractor);
}
}
Defining the mock is difficult here, as I have to mock the underlying JdbcTemplate calls. This could work as follows:
#MockBean
private JdbcTemplate jdbc;
#Test
public void testQuery() {
when(jdbc.query(any(PreparedStatementCreator.class), any(ResultSetExtractor.class))).thenReturn(..);
}
BUT: if I have different sql queries that should produce different results (like above sql1 and sql2), then I'm out of luck and cannot differ the sql during the mock.
What are my chances?
Here:
this.namedJdbc = new NamedParameterJdbcTemplate(jdbc);
That simply makes your code hard to test. Basically you are bypassing dependency injection here, and well, that means: you don't have any control over that field content.
Three options:
turn to a mocking framework like PowerMock(ito) or JMockit that allow you to control calls to new() (not recommended)
change your code, for example using constructor telescoping ... so that you can simply pass in an instance of that class (which can then be mocked with "normal" frameworks like Mockito easily)
as you are already using a framework that has its own ideas of dependency injection (#Autowired for example): step back and do things like that framework implies it to be done.

How do I mock objects in a Java class that has no (non-default) constructor?

I am working on setting up the first unit tests for a legacy Tomcat web service that was not written with testing in mind, and doesn't use Spring. One of the classes I'm having trouble with is a servlet that extends HttpServlet. Here is an abbreviated version of the class.
public class ItemServlet extends HttpServlet {
private ObjectMapper mapper;
private IItemDAO dao;
#Override
public void init() {
mapper = new ObjectMapper();
dao = new GenericItemDao(...);
}
}
Normally I would pass in the external dependencies to the constructor, but the servlets are created by Tomcat based on the web.xml config, which calls only the default constructor and the argument-less init() method. As a result, there doesn't seem to be any way to do dependency injection to allow for my mocks in the unit tests. The only way I can think of doing this is to create a test-only constructor that I can use to instantiate the class from my unit tests, and leave the init() method the way it is for the actual application to call. I could also create a third method that could be called by both the constructor and init() like so:
public class ItemServlet extends HttpServlet {
private ObjectMapper mapper;
private IItemDAO dao;
public ItemServlet(ObjectMapper mapper, IItemDAO dao) {
initDependencies(mapper, dao);
}
private void initDependencies(ObjectMapper mapper, IItemDAO dao) {
this.mapper = mapper;
this.dao = dao;
}
#Override
public void init() {
initDependencies(new ObjectMapper(), new GenericItemDAO(...));
}
}
Is there a cleaner way to unit test these classes?
I would use the opportunity to decouple the code and make it testable. In fact, if you're instantiating a servlet on behalf of Tomcat, just to check if one of its methods returns an expected value, you're way out of the realm of unit tests and rather are doing integration testing.
Using Mock frameworks to work around this will cast the current implementation in stone and make it impossible to change - unless those changes go along with changes to the tests.
I'm an absolute proponent of unit-testing, and I'm also a pragmatist to do what's possible in case I find myself within several framework layers that make it hard to test. In this case (and I only know of your servlet dependency, not of any other - you'll know where you are) I like to separate my code into
atomic, unit-testable units with no framework dependencies
wiring code, that connects the testable code with the frameworks.
The wiring code naturally is not very complex and doesn't get any tests (due to the large number of dependencies). It gets peer-reviewed though.
You could add getters/setters to the class and inject the mocks into the class through there.

Reflection in multiple wrapped objects

In a JUnit test, I want to change the hibernate template in a Spring DAO. This DAO is
annotated with #Transactional so it gets wrapped during runtime and
spyed upon by Mockitos spy()- method. So the DAO will be wrapped a second time by that spy.
So the DAO now has two wrapping objects: One from #Transactional, one from the spy. Due to the fact that it's not known which of those wrapper is created first, I can't set the hibernate template in the DAO via reflection.
How can I set the template in the doubled-wrapped DAO?
[Edit]
Some Source:
/**
* This class gets wrapped by a proxy object because of #Transactional.
*/
#Transactional
public class MyDao implements SomeDaoInterface { ... }
In a test class:
public class MyTestClass {
#Autowired
private MyDao myDao;
#Test
public void myTestMethod() throws Exception {
final MyDao daoSpy = spy(myDao); // Dao gets wrapped with second wrapper
final Field field = MyDao.class.getDeclaredField("template");
field.setAccessible(true);
field.set(daoSpy, mySpecialMockedTemplate); // ERROR: want to inject the template but
// dont know in which wrapper
}
}
Call the setter method instead of accessing the field.
It seems you reflection code is wrong. Use this statement instead :
field.set(daoSpy, mySpecialMockedTemplate);
However looking at your test code, it seems you are using Spring to create the MyDao instance. It seems kind of weird to use reflection to set the template, why not configure it in Spring ?
Or even use an actual setter ? Or make the field protected, so only the unit test can access it.
EDIT : About injection, you could create the DAO instance in your test and have your specialMockedTemplate injected by Mockito. You could writ something like :
#RunWith(MockitoJUnitRunner.class)
public class MyTestClass {
#InjectMocks private MyDao dao;
#Mock SpecialTemplate specialTemplate;
#Test void dao_should_call_the_template_with_parameter_A_and_B() {
// given
// when
dao.someCall("A", "B");
// then
verify(specialTemplate).someCallWith("A", "B");
}
}
A few warnings though, avoid partial mocking if possible (using spies). Avoid moking types you don't own, you should read this blog post entry why this is a bad idea.

Using JMockit to mock autowired interface implementations

We are writing JUnit tests for a class that uses Spring autowiring to inject a dependency which is some instance of an interface. Since the class under test never explicitly instantiates the dependency or has it passed in a constructor, it appears that JMockit doesn't feel obligated to instantiate it either.
Up until now we have been using SpringRunner to have Spring load mock dependencies for us, which works. Two things we don't like about this are 1) the Spring framework has to be loaded and initialized each time running the tests which is not exactly speedy, and 2) we are forced to explicitly create all mock dependencies as real classes, something which JMockit helps eliminate.
Here's a simplified example of what we're testing:
public class UnitUnderTest {
#Autowired
ISomeInterface someInterface;
public void callInterfaceMethod() {
System.out.println( "UnitUnderTest.callInterfaceMethod calling someInterface.doSomething");
someInterface.doSomething();
}
}
So, the question is, is there a way to have JMockit create a mock someInterface?
JMockit will always instantiate a mocked interface (except in the case of a final mock field), but that only occurs in test code. It will not automatically inject the instance into code under test.
You would have to manually inject the mock instance. For example:
public class SomeTest
{
#Autowired UnitUnderTest unitUnderTest;
#Mocked ISomeInterface theMock; // created and assigned automatically
#Test
public void testSomeMethod()
{
Deencapsulation.setField(unitUnderTest, theMock);
//proceed with unit test here
}
}
mockit.Deencapsulation is a Reflection-based utility class that lets you invoke private methods, get/set fields, etc.
You can use org.springframework.test.util.ReflectionTestUtils to explicitly inject your mocked ISomeInterface in your test case.
See documentation
With the hints kindly provided above, here's what I found most useful as someone pretty new to JMockit: JMockit provides the Deencapsulation class to allow you to set the values of private dependent fields (no need to drag the Spring libraries in), and the MockUp class that allows you to explicitly create an implementation of an interface and mock one or more methods of the interface. Here's how I ended up solving this particular case:
#Before
public void setUp() {
IMarketMakerDal theMock = new MockUp <IMarketMakerDal>() {
#Mock
MarketMakerDcl findByMarketMakerGuid( String marketMakerGuid ) {
MarketMakerDcl marketMakerDcl = new MarketMakerDcl();
marketMakerDcl.setBaseCurrencyCode( CURRENCY_CODE_US_DOLLAR );
return marketMakerDcl;
}
}.getMockInstance();
setField( unitUnderTest, theMock );
}
Thanks everyone for the help.
For those people who met
java.lang.IllegalStateException: Missing #Injectable for field ***
or
java.lang.IllegalStateException: Missing #Tested class for field ***
error when using jmockit to mock #autowired field in spring ( or spring boot) framework, I did below two steps to avoid above errors:
use #Tested(fullyInitialized=true) instead of #Tested
https://groups.google.com/forum/#!msg/jmockit-users/uo0S51lSX24/lQhLNN--eJcJ
revert jmockit's version back to 1.18 or previous ones
https://groups.google.com/forum/#!topic/jmockit-users/wMFZggsA8LM
If you have a #Qualifier annotation for the interface, you need to name your #Injectable field exactly as it is named in qualifier.
Here is quote from JMockit doc:
Custom names specified in field annotations from Java EE (#Resource(name), #Named) or the Spring framework (#Qualifier) are used when looking for a matching #Injectable or #Tested value. When such a name contains a - (dash) or . (dot) character, the corresponding camel-cased name is used instead.
For example:
#Component
public class AClass {
#Autowired
private Bean1 bean1;
#Autowired
#Qualifier("my-dashed-name")
private AmqpTemplate rpcTemplate;
}
Unit test class:
public class AClassTest {
#Injectable
private Bean1 bean1;
#Injectable
private AmqpTemplate myDashedName;
#Tested
private AClass aClass = new AClass();
}
Also there is no need to use setFiled for each #Autowired bean, all fields injects automatically when #Tested class instantiated. Tested on JMockit ver. 1.30

Categories

Resources