MockBean stubbing ineffective - spring

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....
}
}

Related

Clone Spring controller instance for testing purpose

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.

Is there a way to test nested objects without the web or persistence layer in Spring Boot?

I'm using JUnit5 to test a Spring Boot application. I want to test a #Service object, which uses #Autowired fields. I would like to mock another #Service object which is indirectly used by my test object. Concretely, I have the following (highly simplified) setup:
Object being tested:
#Service
public class MainService {
private #Autowired SubService subService;
public String test() {
return subService.test();
}
}
SubService:
#Service
public class SubService {
private #Autowired StringService stringService;
public String test() {
return stringService.test();
}
}
StringService:
#Service
public class StringService {
public String test() {
return "Real service";
}
}
Test class used:
#SpringBootTest
public class MainServiceTest {
private #Autowired MainService mainService;
private #MockBean StringService stringService;
#BeforeEach
public void mock() {
Mockito.when(stringService.test()).thenReturn("Mocked service");
}
#Test
public void test() {
assertEquals("Mocked service", mainService.test());
}
}
The above works if I run the test class as a #SpringBootTest, but this loads the full application and is very slow. I also want to avoid #WebMvcTest since I don't need the web server, or #DataJpaTest since I don't need persistence. I don't want to mock SubService, as it contains functionality I want to test together with the MainService.
I tried the following:
#ExtendWith(SpringExtension.class) => throws NoSuchBeanDefinitionException, it seems the autowiring does not work in this case
#ExtendWith(MockitoExtension.class) and using #InjectMocks and #Mock instead of the Spring annotations => as the StringService is not a direct field of the MainService being tested, this does not work.
Is there a way to use the spring dependency injection system without loading the web server or persistence layer, or alternatively not use Spring tests but allow for 'nested' dependency injection?
You can use profiling (i.e Spring #Profile) to avoid loading the whole application. It will look something like below:
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
public MainService mainService() {
return new MainService();
}
#Bean
public SubService subService() {
return new SubService();
}
// mock the StringService
#Bean
public StringService stringService() {
return Mockito.mock(StringService.class);
}
}
then use that profile with `#SpringBootTest(classes = TestConfiguration.class), it will look something like below:
#ActiveProfiles("test")
#SpringBootTest(classes = TestConfiguration.class)
class MainServiceTest {
#Autowired
private MainService mainService;
#Test
public void test() {
// configure behavior using apis like when(), basically however you
// want your mock to behave
}
}
This will load only the beans defined in the class TestConfiguration.
NOTE: Since your question is more about how to avoid loading the whole application, I've answered focusing on that. The above approach will get the job done, but I'd prefer constructor wiring over any other mode of dependency injection on any given day, it's easier to maintain and test(like cases where you want to mock).

Unit Testing a class with ServiceLocatorFactoryBean Autowired

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;
}
}

Resetting Mockito mocks, provided as Spring beans, between tests?

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);
}
}

Spring HATEOAS Resource assembler is not instantiated in unit test

I am trying to write a unit test for a REST controller which generates HATEOAS links via Resource assembler class. Everything is OK in production, but with the unit test Resource assembler class is not being injected into the controller.
my resource assembler class is:
#Component
public class ModelResourceAssembler extends ResourceAssemblerSupport<Model, ModelResource> {
public ModelResourceAssembler() {
super(ModelRestController.class, ModelResource.class);
}
#Bean
public ModelResourceAssembler modelResourceAssembler(){
return new ModelResourceAssembler();
}
#Override
public ModelResource toResource(Model model) {
...
}
}
The controller is:
#Controller
#RequestMapping("/demo")
#ComponentScan(basePackages = {"com.foo.demo"} )
public class ModelRestController {
#Autowired
private ModelPersistenceHandler modelPersistenceHandler;
#Autowired
private ModelResourceAssembler modelResourceAssembler;
...
}
And the unit test:
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes= {ModelResourceAssembler.class, ModelRestController.class})
public class ModelRestControllerTest {
private MockMvc mockMvc;
#InjectMocks
private ModelRestController modelRestController;
#Mock
private ModelPersistenceHandler modelPersistenceHandler;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(modelRestController).build();
}
...
}
No matter what I do the ModelResourceAssembler instance is always null. Since the application is Spring Boot it does not have the WebCoonfig classes and autowired WebApplicationContext is always null, so I cannot (and really don't want to since I am running a unit test) instantiate MockMvc via webAppContextSetup
The solution ended up being quite simple: I needed to add one line to my test:
#Spy
private ModelResourceAssembler modelResourceAssembler;
And the bean was instantiated and properly wired
In your example you use #InjectMocks but don't declare a mock for ModelResourceAssembler. You don't get an instance out of nowhere.
You use the MockitoJUnitRunner.class. It has no idea of Spring beans. For testing Spring applications you rather want to use SpringJUnit4ClassRunner.class.
If i may suggest, if you use constructor injection for your controller then you can just mock the dependency and not need spring junit test runner stuff.

Resources