Injecting mock before Spring's post-construct phase - java

Basically, the question is in the title.
I faced a problem that in post-construct phase my bean (that is autowired in the bean that is going through post-construct phase right now) is already mocked, but all the behavior described by Mockito.when() doesn't work, all the calls return null.
While searching I found this solution.
But is it possible to make it work without using any 3rd party libraries?
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ContextConfiguration(classes = TestApplicationConfiguration.class)
public class ServiceTest {
#Autowired
#Qualifier("test")
private PCLPortType pclPortType;
#MockBean
private ClearingHelper сlearingHelper;
#MockBean
private OrganizationCacheRepository organizationCacheRepository;
#Before
public void setup() throws Exception{
OperationResultWithOrganizationSystemIdMappingList res = new OperationResultWithOrganizationSystemIdMappingList();
when(clearingHelper.getOrgIdSystemIdMapping(any(Keycloak.class))).thenReturn(res);
}
#Test
public void test() throws Exception{
pclPortType.call("123");
}
}
Test config:
#TestConfiguration
public class TestApplicationConfiguration {
#Bean(name = "test")
public PCLPortType pclPortTypeForTest() throws JAXBException {
...
}
#Bean
public Keycloak keycloak() {
return Mockito.mock(Keycloak.class);
}
}
Component where I want to get mocked beans:
#Component
public class OrganizationCacheJob {
private static final Logger logger =
LogManager.getLogger(OrganizationCacheJob.class);
private final ObjectFactory<Keycloak> factory;
private final ClearingHelper clearingHelper;
private final OrganizationCacheRepository organizationCacheRepository;
#Autowired
public OrganizationCacheJob(ObjectFactory<Keycloak> factory,
ClearingHelper clearingHelper,
OrganizationCacheRepository organizationCacheRepository) {
this.factory = factory;
this.clearingHelper = ClearingHelper;
this.organizationCacheRepository = organizationCacheRepository;
}
#PostConstruct
public void updateCacheRepository() {
doUpdateCacheRepository();
}
#Scheduled(cron = "${organization.cache.schedule}")
public void start() {
logger.info("Starting update organization cache.");
doUpdateCacheRepository();
logger.info("Job finished.");
}
private void doUpdateCacheRepository() {
try {
Keycloak keycloak = factory.getObject();
OperationResultWithOrganizationSystemIdMappingList orgIdSystemIdMapping = clearingHelper.getOrgIdSystemIdMapping(keycloak);
if (orgIdSystemIdMapping != null) {
orgIdSystemIdMapping.getContent().forEach(o -> organizationCacheRepository.saveOrgIdsSystemsIdsMappings(o.getOrgId(), o.getId()));
logger.debug("Was saved {} orgIds", orgIdSystemIdMapping.getContent().size());
}
} catch (Exception e) {
logger.error("Error fetching whole mapping for org and systems ids. Exception: {}", e);
}
}
}
So, in post-construct phase of OrganizationCacheJob I want to get res when calling clearingHelper, but instead I get null.
ClearingHelper is a regular Spring bean marked as a #Component with public methods.

Ahh ok I just realized - when you start your test case, whole env is up and running first, then you advance to testing phase. So, translating to your case - first you got injection and post-constructs called, then #Before method is done, thus the result.
So as you can see, code says more than all the words you could put in your original post.
If it is possible for you, use spies insteed of mocks. If it is not possible to construct that, you will have to redesign your tests to not rely on post construct.
In this case, since you want the same post-construct behavior for every test case, provide own factory method for given mock (like you did with keycloak) and move when-doReturn there. It will be guaranteed that it will happen before post construct.

Related

JUnit5 test case calls #Transactional function

Given a TestClass with TestMethod for an integration test (spawning MySQL Testcontainer behind the scenes)
#SpringBootTest
public class InsertAnythingIntegrationTest {
private DoAnythingHandler handler;
#Autowired
private AnythingRepository anythingRepository;
#BeforeEach
void beforeEach() {
handler = new DoAnythingHandler(anythingRepository);
}
#Test
void handle_shouldAddEntry_givenValidValue() {
handler.insertSomething(new Entity(x,y,z));
assertThat(anythingRepository.findAll()).isEqualTo(1);
}
}
and the Handler implementation annotated with #Transactional
#Component
public class DoAnythingHandler() {
#Autowired
private AnythingRepository anythingRepository;
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Transactional
public void insertOrUpdateSomething(Entity entity) {
var existingEntity = anythingRepository.findById(entity.getId());
if (existingEntity != null) {
existingEntity.valueX = entity.x;
existingEntity.valueY = entity.Y;
existingEntity.valueZ = entity.Z;
} else {
anythingRepository.save(entity);
}
applicationEventPublisher.publishEvent(new AnyFurtherEventUpdatingDb(entity.X, entity.Y));
}
}
If I run this test, the transaction is never opened since it needs to be called from Bean to Bean to apply the #Transactional annotation.
How can I mock a Bean calling this method in my test case.
I don't want my test case to be #Transactional since I am not able to assert anything. This handler is my UnitOfWork and I want no further abstraction layer whatever in place.
How to approach this?
#Autowired
private DoAnythingHandler handler;
did the trick for me.
I thought I tried this before, but maybe I did something wrong before.
Thanks to everyone!

Strict #MockBean in a Spring Boot Test

I am developing a Spring Boot application. For my regular service class unit tests, I am able to extend my test class with MockitoExtension, and the mocks are strict, which is what I want.
interface MyDependency {
Integer execute(String param);
}
class MyService {
#Autowired MyDependency myDependency;
Integer execute(String param) {
return myDependency.execute(param);
}
}
#ExtendWith(MockitoExtension.class)
class MyServiceTest {
#Mock
MyDependency myDependency;
#InjectMocks
MyService myService;
#Test
void execute() {
given(myDependency.execute("arg0")).willReturn(4);
myService.execute("arg1"); //will throw exception
}
}
In this case, the an exception gets thrown with the following message (redacted):
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'execute' method:
myDependency.execute(arg1);
- has following stubbing(s) with different arguments:
1. myDependency.execute(arg0);
In addition, if the stubbing was never used there would be the following (redacted):
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at MyServiceTest.execute()
However, when I use #MockBean in an integration test, then none of the strict behavior is present. Instead, the stubbed method returns null because the stubbing "fails" silently. This is behavior that I do not want. It is much better to fail immediately when unexpected arguments are used.
#SpringBootTest
class MyServiceTest {
#MockBean
MyDependency myDependency;
#Autowired
MyService myService;
#Test
void execute() {
given(myDependency.execute("arg0")).willReturn(4);
myService.execute("arg1"); //will return null
}
}
Is there any workaround for this?
Yes there are some workarounds but it is quite involved.
It may be better to just wait for Mockito 4 where the default will be strict mocks.
The first option:
Replace #MockBean with #Autowired with a test configuration with #Primary ( this should give the same effect as #MockBean, inserting it into the application as well as into the test )
Create a default answer that throws an exception for any unstubbed function
Then override that answer with some stubbing - but you have to use doReturn instead of when thenReturn
// this is the component to mock
#Component
class ExtService {
int f1(String a) {
return 777;
}
}
// this is the test class
#SpringBootTest
#RunWith(SpringRunner.class)
public class ApplicationTests {
static class RuntimeExceptionAnswer implements Answer<Object> {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
throw new RuntimeException(
invocation.getMethod().getName() + " was not stubbed with the received arguments");
}
}
#TestConfiguration
public static class TestConfig {
#Bean
#Primary
public ExtService mockExtService() {
ExtService std = Mockito.mock(ExtService.class, new RuntimeExceptionAnswer());
return std;
}
}
// #MockBean ExtService extService;
#Autowired
ExtService extService; // replace mockBean
#Test
public void contextLoads() {
Mockito.doReturn(1).when(extService).f1("abc"); // stubbing has to be in this format
System.out.println(extService.f1("abc")); // returns 1
System.out.println(extService.f1("abcd")); // throws exception
}
}
Another possible but far from ideal option: instead of using a default answer is to stub all your function calls first with an any() matcher, then later with the values you actually expect.
This will work because the stubbing order matters, and the last match wins.
But again: you will have to use the doXXX() family of stubbing calls, and worse you will have to stub every possible function to come close to a strict mock.
// this is the service we want to test
#Component
class ExtService {
int f1(String a) {
return 777;
}
}
// this is the test class
#SpringBootTest
#RunWith(SpringRunner.class)
public class ApplicationTests {
#MockBean ExtService extService;
#Test
public void contextLoads() {
Mockito.doThrow(new RuntimeException("unstubbed call")).when(extService).f1(Mockito.any()); // stubbing has to be in this format
Mockito.doReturn(1).when(extService).f1("abc"); // stubbing has to be in this format
System.out.println(extService.f1("abc")); // returns 1
System.out.println(extService.f1("abcd")); // throws exception
}
}
Yet another option is to wait until after the test finishes using the mock, and then use
verifyNoMoreInteractins();
As mentioned in this comment, this GitHub issue in the spring-boot project addresses this same problem and has remained open since 2019, so it's unlikely that an option for "strict stubs" will be available in #SpringBootTest classes anytime soon.
One way that Mockito recommends to enable "strict stubs" is to start a MockitoSession with Strictness.STRICT_STUBS before each test, and close the MockitoSession after each test. Mockito mocks for #MockBean properties in #SpringBootTest classes are generated by Spring Boot's MockitoPostProcessor, so a workaround would need to create the MockitoSession before the MockitoPostProcessor runs. A custom TestExecutionListener can be implemented to handle this, but only its beforeTestClass method would run before the MockitoPostProcessor. The following is such an implementation:
public class MyMockitoTestExecutionListener implements TestExecutionListener, Ordered {
// Only one MockitoSession can be active per thread, so ensure that multiple instances of this listener on the
// same thread use the same instance
private static ThreadLocal<MockitoSession> mockitoSession = ThreadLocal.withInitial { null };
// Count the "depth" of processing test classes. A parent class is not done processing until all #Nested inner
// classes are done processing, so all #Nested inner classes must share the same MockitoSession as the parent class
private static ThreadLocal<Integer> depth = ThreadLocal.withInitial { 0 };
#Override
public void beforeTestClass(TestContext testContext) {
depth.set(depth.get() + 1);
if (depth.get() > 1)
return; // #Nested classes share the MockitoSession of the parent class
mockitoSession.set(
Mockito.mockitoSession()
.strictness(Strictness.STRICT_STUBS)
.startMocking()
);
}
#Override
public void afterTestClass(TestContext testContext) {
depth.set(depth.get() - 1);
if (depth.get() > 0)
return; // #Nested classes should let the parent class end the MockitoSession
MockitoSession session = mockitoSession.get();
if (session != null)
session.finishMocking();
mockitoSession.remove();
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
Then, MyMockitoTestExecutionListener can be added as a listener in test classes:
#SpringBootTest
#TestExecutionListeners(
listeners = {MyMockitoTestExecutionListener.class},
mergeMode = MergeMode.MERGE_WITH_DEFAULTS
)
public class MySpringBootTests {
#MockBean
Foo mockFoo;
// Tests using mockFoo...
}

Axon Testing: Missing Context

In an Axon-SpringBoot App I have an aggregate that uses an injected DAO in some of its command handlers.
For instance:
#Aggregate
class MyAggregate {
#CommandHandler
public MyAggregate (CreateMyAggregateCommand command, #Autowired MyAggregateDao dao) {
final SomeProperty = command.getSomePoprtery();
if (dao.findBySomeProperty(someProperty) == null) {
AggregateLifeCycle.apply(
new MyAggregateCreatedEvent(command.getIdentifier(),
someProperty);
} else {
// Don't create, already exits with some property
// report ...
}
}
}
A standard test like
#Test
void creationSucceeds () {
aggregateTestFixture = new AggregateTestFixture<>(MyAggregate.class);
final CreateMyAggregateCommand command = new CreateMyAggregateCommand(...);
final MyAggregateCreatedEvent = new MyAggregateCreatedEvent(...);
aggregateTestFixture
.givenNoPriorActivity()
.when(command)
.expectEvents(event);
}
fails with:
org.axonframework.test.FixtureExecutionException: No resource of type
[com.xmpl.MyAggregateDao] has been registered. It is required
for one of the handlers being executed.
How can I provide a test implementation?
Solution 1: Mocking
Since this is about unit testing and my question involves database calls (external service), mocking seems applicable as long as testing is about isolated aggregate behavior only.
#Test
void creationSucceeds () {
aggregateTestFixture = new AggregateTestFixture<>(MyAggregate.class);
aggregateTestFixture.registerInjectableResource(
Mockito.mock(MyAggregateDao.class));
}
Solution 2: Real Injection (jUnit 5)
This one works for me:
Get small library for Spring jUnit5 testing support from this github repo
Annotate test classes:
#SpringBootTest(classes = {SpringTestConfig.class})
#ExtendWith(SpringExtension.class)
public class MyAggregateTest {
// ...
}
Place application.properties in src/test/resources
Write Spring Test Configuration which starts a fully functioning Spring container:
#EnableAutoConfiguration
public class SpringTestConfig {
// Set up whatever you need
#Bean
#Autowired
MyAggregateDao myDao (DataSource dataSource) {
// ...
}
#Bean
#Autowired
EventStorageEngine eventStorageEngine () {
return new InMemoryEventStorageEngine();
}
}
Inject directly into your tests, configure AggregateTestFixture
private AggregateTestFixture<MyAggregate> aggregateTestFixture;
#Autowired
private MyAggregateDao myDao;
#BeforeEach
void beforeEach () {
aggregateTestFixture = new AggregateTestFixture<>(MyAggregate.class);
// We still need to register resources manually
aggregateTestFixture.registerInjectableResource(myDao);
}
With jUnit 4
Setting up a test configuration that starts a Spring container with jUnit 4 is a bit different but there's enough documentation out there. Start here

Excluding an ApplicationListener #Component in Spring Boot during tests

I am trying to have my test unit up and running, and I have encountered a weird issue. My application uses an ApplicationListener class annotated as a #Component to perform an operation during startup.
During tests I have mocked the service that contains the logic, but I found that even though Mockito's when instructions work well in controller scope, the bean is not initialized for this ApplicationListener class: instead of returning what I define in the test unit, it returns either false or null - depending on the data type returned by each method in the service.
Since I have not found any way to initialize the mocked service from the test unit for the ApplicationListener class, I have decided to exclude it. To do so I have tried different approaches, being the one most often used that of creating a test application context and change its configuration. Unfortunately, nothing I have seen is working - so I am here asking for help. If possible, I would prefer not touching the ApplicationListener class and do all related coding in the test code.
I am interested in any of the two possible solutions, if they can be done:
1.- Get the mocked behaviour during the ApplicationListener execution, but I have read somewhere that this cannot be done
2.- Exclude the #Component from the test unit somehow.
TestUnit.Java:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class TestConfigurationService {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private MockService mockService;
private void initMockBean () throws Exception {
when(mockService.isDoingSomething()).thenReturn(true);
}
#Before
public void setup() throws Exception {
// Spring mock context application setup
this.mockMvc = webAppContextSetup(webApplicationContext).build();
// Initialize ConsulService mock bean
initMockBean ();
}
}
TestApplication.java
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages="my.base.package", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = StartupConfiguration.class))
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Besides what is shown in the code, I have also tried this annotation in file TestApplication.java:
#SpringBootApplication(exclude={StartupConfiguration.class})
StartupConfiguration.java
#Component
public class StartupConfiguration implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ConfigurationService configurationService;
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
try {
configurationService.updateConfiguration();
} catch (Exception e) {
throw new RuntimeException ("Error", e);
}
}
}
ConfigurationService.java
public interface ConfigurationService {
public void updateConfiguration () throws Exception;
}
ConfigurationServiceImpl.java
#Service
#Transactional
public class ConfigurationServiceImpl implements ConfigurationService {
#Autowired
private MService mockService;
#Override
public void updateConfiguration() throws Exception {
if (mockService.isDoingSomething()==false)
throw new Exception ("Something went wrong");
}
}
Versions:
Spring Boot 1.5.4.RELEASE,
Java 1.8
You can create mock bean of the same type and mark it with #Primary annotation to replace real bean. You can achieve this by having test such configuration:
#Configuration
#Import(TestApplication.class)
public class TestConfiguration {
#Bean
#Primary
public ConfigurationService configurationService() {
return Mockito.mock(ConfigurationService.class);
}
}
then get this mock in test:
...
public class TestConfigurationService {
...
#Autowired
ConfigurationService configurationService;
#Before
public void setUp() {
when(mockService.isDoingSomething()).thenReturn(true);
}
}
Thanks, araxn1d. Your answer gave me the clue to solve this issue.
I mocked the StartupConfiguration class in TestUnit.java:
#MockBean
private StartupConfiguration startupConfiguration;
Though in this case I was lucky: application listeners don't have returning methods, so they don't need when test configuration. If I had required that some method there returned for example true or a value, this method would not apply.
But at least for application listeners, this is enough.

Using an #Autowired resource with "try with resource"

Given the following:
public class ResourceOne implements AutoCloseable {...}
with an instance of ResourceOne instantiated in (Spring) XML config.
How should this object (when autowired) be used with the "try-with-resources Statement", since you are required to instantiate the resource in the try block?
One approach could be to use a reference (see below), but this isn't really optimal.
public class Test {
#Autowired
ResourceOne test;
//...
public void execute()
{
//...
try (ResourceOne localTest = test)
{
localTest.init()
localTest.readLine();
//...
}
}
AFAIK I think the approach that you have taken seems to be the only way.
try (ResourceOne localTest = test)
{
localTest.init()
localTest.readLine();
//...
}
I am assuming that you have your autowired resource declared with prototype scope though right.
#Bean
#Scope(value="prototype", proxyMode=ScopedProxyMode.DEFAULT)
public Resource1 resource1() {
return new Resource1();
}
A solution could be to autowire the spring application context and have it retrieve an instance of the bean at runtime.
public class Test {
#Autowired
ApplicationContext applicationContext
//...
public void execute()
{
//...
try (ResourceOne localTest = applicationContext.getBean(ResourceOne.class))
{
localTest.init()
localTest.readLine();
//...
}
}
This way you don't risk having a non-functional instance of ResourceOne in your code that could potentially be used again from another method and result in an exception because is't already been closed.

Categories