Is it possible to clone an instance of a Spring #Controller in a JUnit 5 test?
I would like to do so for the following reasons :
Mocking pollutes the context for next tests and DirtiesContext has a huge impact on performance. So I don't want to use Mocks unless the modification sticks to the test class.
I would like to run tests in parallel so modifying a shared controller instance at runtime (with ReflectionUtils for example) will produce unpredictable behavior.
I don't want to set a controller as prototype as it is not the case at runtime and Spring is already wiring all dependencies.
I was thinking to inject the controller in the test class with #Autowired as usual and then making a deep copy of it with SerializationUtils like described in this post but I hope there could be a more robust approach than serializing / deserializing in every test class.
What makes tests failing in parallel mode?
The end-to-end tests are using common services. For example, I have a controller used in those two tests.
#RestController
#RequestMapping("/api/public/endpointA/")
public class SomeController {
#Autowired
private MyService myService;
#GetMapping("/{id}")
public Something getSomethingById(#PathVariable int id) {
return myService.getSomethingById(id);
}
}
The first test just check the standard usage.
class SomeControllerTest {
#Autowired
#InjectMocks
private SomeController someController;
#Test
void testGetSomethingById() {
Assertions.assertEquals(
1,
// I use some custom wrapper for the HTTP methods calls on
// the controller like this to ease the mock mvc setup.
someController.getAndParse(
HttpStatus.OK,
Something.class,
"/{id}",
1
).getId()
);
}
}
The second case test what happens when error occurs.
class SomeControllerExceptionTest {
#Autowired
private SomeController someController;
#SpyBean
private MyService myService;
#BeforeEach
public void before() {
// Mock to test error case
doThrow(RuntimeException.class).when(myService)
.getSomethingById(anyInt());
}
#Test
void testGetSomethingById() {
Assertions.assertThrows(
someController.getAndParse(
HttpStatus.OK,
Something.class,
"/{id}",
1
)
);
}
}
By mocking in the second test, I'm not sure the first test won't use the mocked instance of the second test when tests are running in parallel.
Instantiate the controller in the test method yourself, mock the dependencies inside the method and inject them in the controller.
Same situation described above but I mock on a new instance of the controller.
#RestController
#RequestMapping("/api/public/endpointA/")
public class SomeController {
#Autowired
private MyService myService;
#GetMapping("/{id}")
public Something getSomethingById(#PathVariable int id) {
return myService.getSomethingById(id);
}
}
Test both cases in one test.
class SomeControllerTest {
#Autowired
private SomeController someController;
private SomeController someControllerMocked;
#BeforeEach
public void before() {
someControllerMocked = new SomeController();
MyService mockedService = mock(MyService.class);
doThrow(RuntimeException.class).when(mockedService)
.getSomethingById(anyInt());
ReflectionTestUtils.setField(
someControllerMocked,
"myService",
mockedService
);
}
#Test
void testGetSomethingById() {
Assertions.assertEquals(
1,
someController.getAndParse(
HttpStatus.OK,
Something.class,
"/{id}",
1
).getId()
);
}
#Test
void testGetSomethingByIdException() {
Assertions.assertThrows(
someControllerMocked.getAndParse(
HttpStatus.OK,
Something.class,
"/{id}",
1
)
);
}
}
Yes! It's working and the context is not polluted. Ok let's say I have 10 services injected in my controller actually. I will have to do ReflectionUtils#setField 9 times for the legacy services and 1 time for the mock. Looks a bit ugly.
With AutowireCapableBeanFactory to the rescue. I've managed to clean this a little bit.
Same situation, but SomeController has 10 autowired services.
class SomeControllerTest {
#Autowired
private SomeController someController;
private SomeController someControllerMocked;
#Autowired
private AutowireCapableBeanFactory beanFactory;
#BeforeEach
public void before() {
someControllerMocked = new SomeController();
// Replace the 9 ReflectionUtils#setField calls with this
beanFactory.autowireBean(someControllerMocked);
MyService mockedService = mock(MyService.class);
doThrow(RuntimeException.class).when(mockedService)
.getSomethingById(anyInt());
ReflectionTestUtils.setField(
someControllerMocked,
"myService",
mockedService
);
}
#Test
void testGetSomethingById() {
Assertions.assertEquals(
1,
someController.getAndParse(
HttpStatus.OK,
Something.class,
"/{id}",
1
).getId()
);
}
#Test
void testGetSomethingByIdException() {
Assertions.assertThrows(
someControllerMocked.getAndParse(
HttpStatus.OK,
Something.class,
"/{id}",
1
)
);
}
}
I think it's the best approach I've found.
Related
I have one repository class which which implements CrudRepository. Then in service class I have auto wired this repositary. Then in controller class I have autowired this service.
I want to write test cases of controller Class. I am using below configuration.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class XYZControllerTest {
MockMvc mockMvc;
#Mock
private XYZController xyzController;
#Autowired
private TestRestTemplate template;
#Autowired
XYZRepository xyzRepository;
#Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(xyzController).build();
}
#Test
public void testPanelShouldBeRegistered() throws Exception {
HttpEntity<Object> xyz = getHttpEntity("{\"name\": \"test 1\", \"email\": \"test10000000000001#gmail.com\","
+ " \"registrationNumber\": \"41DCT\",\"registrationDate\":\"2018-08-08T12:12:12\" }");
ResponseEntity<XYZ> response = template.postForEntity("/api/xyz", xyz, XYZ.class);
}
}
My problem is that when I run test case, data is going to insert in DB which is used for application. Can I test it without inserting data in DB.
Conceptually when we are testing services we mock repositories instead of injection.
You need to mock your repository and setup behavior for returning data.
An example :
#MockBean
XYZRepository xyzRepository;
#Test
public void test() {
// other mocks
//
when(xyzRepository.findAll()).thenReturn(Arrays.asList(new XYZ()));
// service calls
// assertions
}
I have a configuration class with a few MockBeans replacing actual beans in context for tests.
#Configuration
public class MyTestConfig {
#MockBean
private MyService myService;
}
I use those mocks in my tests:
#Import({ MyTestConfig .class })
public class MyTest {
#Autowired
private MyService myService;
#Test
public void aTest() {
...
}
}
First the idea was to add the stubbing in this MyTestConfig configuration class, so that the mock is pre-made for all tests, so I did it in a #PostConstruct method, and it worked just fine - the mock in test did return the expected value:
#PostConstruct
public void init() {
when(myService.foo("say hello")).thenReturn("Hello world");
}
It turned out though, that constructing a pre-made mock suitable for all test can be tricky, so we decided to move the stubbing to tests.
#Test
public void aTest() {
when(myService.foo("say hello")).thenReturn("Hello world");
}
And this doesn't work - the stubbed method returns null. We want to leave MockBeans in the configuration class, but stub them in tests, so any advice on why the stubbing is ineffective?
Spring Boot 2.0.5, Mockito 2.22.0
Yes, stubbing should be performed inside their respective test cases (unless you have a test class that shares the stubbing scenarios but it all comes down to preference).
However, for creating #MockBeans, you would need to use a #SpringBootTest in order to get the actual beans replaced with mocks. This could be done as simply as this example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private MyTestClass testClass;
#MockBean
private MyService service;
#Test
public void myTest() {
// testing....
}
}
I have a Interface which is registered as part of ServiceLocatorFactoryBean. The main purpose of this Interface is to act as a factory.
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.html
I have "autowired" this Interface in various classes, that I want to test now with Mockito.
The issue is Mockito doesn't support interfaces. How can inject a mock of this interface in the class I am testing.
The only alternative I see is to run the test using SpringJunitRunner and providing an Application Context which has the bean configurations. But this is too verbose.
I take it you'd like to spy on the implementation that Spring generated for your interface?! That's close to impossible to achieve with what you have so far... However there are at least the following alternatives below.
Suppose we have the following setup:
public interface MyService {
String doIt();
}
#Component
public static class ServiceConsumer {
#Autowired
private MyService service;
public String execute() {
return service.doIt();
}
}
0) Later edit: while roaming around, I found that it may be possible to spy and even replace an autowired field with a mock, and fairly easy too, using Springockito-annotations.
#RunWith(SpringJUnit4ClassRunner.class)
#ComponentScan
#ContextConfiguration(loader = SpringockitoAnnotatedContextLoader.class, classes = {SpringockitoConsumerTest.class})
public class SpringockitoConsumerTest {
#WrapWithSpy(beanName = "myService")
#Autowired
private MyService mockService;
#Autowired
private ServiceConsumer consumer;
#Test
public void shouldConsumeService() {
assertEquals("allDone", consumer.execute());
verify(mockService).doIt();
}
}
If Springockito-annotations is out of the question, please see the 2 original suggestions below
1) You could just create your mock of the interface and auto-inject it Mockito in your bean. This is the simplest solution (I could think of at the time of writing) but it does not ensure that the #Autowired annotation was not forgotten in the consumer (perhaps a dedicated test could be added?!):
public class AutoInjectMocksConsumerTest {
#Mock
private MyService serviceMock;
#InjectMocks
private ServiceConsumer consumer = new ServiceConsumer();
#Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
when(serviceMock.doIt()).thenReturn("allDone");
}
#Test
public void shouldConsumeService() {
assertEquals("allDone", consumer.execute());
verify(serviceMock).doIt();
}
}
2) Alternatively as you also said, you could run it with the SpringJunitRunner making a minimum of effort to define and instantiate the necessary Spring context while also providing your own service mock. Albeit people may complain this solution is not that clean, I find it sufficiently elegant and it also validates that the #Autowired annotation was not forgotten in the consumer implementation.
#RunWith(SpringJUnit4ClassRunner.class)
#Configuration
#ComponentScan
#ContextConfiguration(classes = {SpringAutowiringConsumerTest.class})
public class SpringAutowiringConsumerTest {
#Autowired
private MyService mockService;
#Autowired
private ServiceConsumer consumer;
#Test
public void shouldConsumeService() {
assertEquals("allDone", consumer.execute());
verify(mockService).doIt();
}
#Bean
public MyService mockService() {
MyService serviceMock = mock(MyService.class);
when(serviceMock.doIt()).thenReturn("allDone");
return serviceMock;
}
}
I have a Java application that uses Spring's dependency injection. I want to mock out a bean, and verify that it receives certain method calls.
The problem is that Mockito does not reset the mock between tests, so I cannot correctly verify method calls on it.
My unit under test:
public class MyClass {
#Resource
SomeClientClass client;
public void myMethod() {
client.someMethod();
}
}
The unit test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = UnitTestConfig.class)
public class MyClassTest {
#Resource
SomeClientClass client;
#Test
public void verifySomething() {
// ...
Mockito.verify(client).publish();
}
}
Finally,
#Configuration
public class UnitTestConfig {
#Bean
SomeClientClass client() {
return Mockito.mock(SomeClientClass.class);
}
}
Though I could hack my way around this problem by manually resetting mocks between tests, I wonder if there's a cleaner / more idiomatic approach.
I had to add this at the start:
#BeforeEach
void setup() {
Mockito.reset(...mockBeans);
...
}
Author not explained why he needs it, I can put more details.
Combining Spring's dependency injection with Mockito in this way not the best approach.
It leads to errors, because same Mocks will be reused between different tests!
This means that verify() will work incorrectly. It will accumulate method invocations from different tests. For example you will get "Wanted 1 time:" - "But was 2 times".
Most generic solution for this in Mockito is using #InjectMocks.
This annotation doing 2 important things:
actually injecting all #Mock fields into class annotated with #InjectMocks
resets each #Mock annotated class. (so, verify() will not accumulate invocations from different tests)
Code example:
#RunWith(MockitoJUnitRunner.class)
public class SomeSpringConverterTest {
#InjectMocks
private SomethingToJsonNodeSpringConverter someSpringConverter;
#Mock
private SomethingDatamodelService someDatamodelService;
#Test
public void convertOriginalContainerTest() {
SomethingContainer someContainer = buildSomeContainer("aa", "bb");
Mockito.when(someDatamodelService.getAttributes()).thenReturn(Arrays.asList("aa", "bb"));
JsonNode node = someSpringConverter.convert(someContainer, JsonNode.class);
Mockito.verify(someDatamodelService.getAttributes());
assertTrue(node.get("aa") != null);
}
#Test
public void convertOriginalContainerTest() {
SomethingContainer someContainer = buildSomeContainer("aa", "bb");
Mockito.when(someDatamodelService.getAttributes()).thenReturn(Arrays.asList("aa", "bb"));
JsonNode node = someSpringConverter.convert(someContainer, JsonNode.class);
Mockito.verify(someDatamodelService.getAttributes());
assertTrue(node.get("bb") != null);
}
}
I have the following service:
#Service
public class PlayerValidationService {
#Autowire
private EmailService emailService;
public boolean validatePlayerEmail(Player player) {
return this.emailService.validateEmail(player.getEmail());
}
Now in my junit test class i'm using a different 3rd service that uses PlayerValidationService :
public class junit {
#autowire PlayerAccountService playerAccountService ;
#Test
public test() {
this.playerAccountService .createAccount();
assertAllSortsOfThings();
}
Is it possible to mock the EmailService within the PlayerAccountService when using annotation based autowiring? (for example make the mock not checking the validation of the email via the regular email provider we work with)
thanks.
There are a couple of ways in which you could do this. First the simplest option is to ensure that your service provides a setEmailService(EmailService) method. In which case you just replace the Spring-injected implementation with your own.
#Autowired
private PlayerValidationService playerValidationService;
#Mock
private EmailService emailService;
#Before
public void setup() {
initMocks(this);
playerValidationService.setEmailService(emailService);
}
A shortcoming of that approach is that an instance of the full-blown EmailService is likely to be created by Spring. Assuming that you don't want that to happen, you can use 'profiles'.
In your test packages, create a configuration class which is only active in a particular profile:
#Configuration
#Profile("mockemail")
public class MockEmailConfig {
#Bean(name = "emailService")
public EmailService emailService() {
return new MyDummyEmailService();
}
}
And add an annotation to your test to activate that profile:
#ActiveProfiles({ "mockemail" })
public class PlayerValidationServiceTest {
//...
}