Injecting a mock in Spring application context creates a copy - java

I have a test class that looks like this :
#SpringApplicationConfiguration(classes = RecipesApplicationTest.class) // This test class will configure its own context
#ComponentScan("com.mysmartfridge.domain.recipes") // Scan recipes domain package so that domain services are available
#Import(RecipesApplication.class) // Load the bean that we want to test.
public class RecipesApplicationTest {
#ClassRule
public static final SpringClassRule SCR = new SpringClassRule();
#Rule
public final SpringMethodRule SMR = new SpringMethodRule();
#Autowired
private RecipesRepository recipesRepository;
private final RecipesRepository recipesRepositoryMock = Mockito.mock(RecipesRepository.class); // final mocks because they are given to spring context.
// Get the service to test from the context
#Autowired
RecipesApplication recipesApplication;
#Test
public void addingARecipeShouldMakeItAvailableInRandomRecipes() {
//given
RecipeDto dto = new RecipeDto();
dto.title="test";
dto.ingredients = new ArrayList<>();
dto.steps = new ArrayList<>();
final List<Recipe> recipesInMock = new ArrayList<>();
Mockito.when(recipesRepository.save(Mockito.any(Recipe.class))).thenAnswer(new Answer<Recipe>() {
#Override
public Recipe answer(InvocationOnMock aInvocation) throws Throwable {
Recipe arg = aInvocation.getArgumentAt(0, Recipe.class);
recipesInMock.add(arg);
return arg;
}
});
Mockito.when(recipesRepository.findAll()).thenAnswer(new Answer<List<Recipe>>() {
#Override
public List<Recipe> answer(InvocationOnMock aInvocation) throws Throwable {
return recipesInMock;
}
});
//when
dto = recipesApplication.createRecipe(dto);
RecipeDto randomDto = recipesApplication.findRandomRecipe();
//then
Assertions.assertThat(randomDto).isEqualTo(dto);
}
// Inject the recipeRepository mock in the context
#Bean
RecipesRepository recipesRepository() {
return recipesRepositoryMock;
}
}
My problem is that the two fields recipesRepositoryMock and recipesRepository are not the same object. Thus, if i try to replace recipesRepository with recipesRepositoryMock when setting up my answers (i.e. if I do when(recipesRepositoryMock.save()).thenAnswer(...)), it doesn't work, then custom answer is never called.
I would like to be able to get rid of the #Autowired recipesRepository which is kind of a duplicate with recipesRepositoryMock...
Is it because of a proxy added by spring around my bean ?

You could better remove
private final RecipesRepository recipesRepositoryMock = Mockito.mock(RecipesRepository.class);
If you want to mock a Spring bean then you have to create it for Spring as use #Autowired.
So you test could look like:
#SpringApplicationConfiguration(classes = RecipesApplicationTest.class) // This test class will configure its own context
#ComponentScan("com.mysmartfridge.domain.recipes") // Scan recipes domain package so that domain services are available
#Import(RecipesApplication.class) // Load the bean that we want to test.
public class RecipesApplicationTest {
#ClassRule
public static final SpringClassRule SCR = new SpringClassRule();
#Rule
public final SpringMethodRule SMR = new SpringMethodRule();
#Autowired
private RecipesRepository recipesRepository;
// Get the service to test from the context
#Autowired
RecipesApplication recipesApplication;
#Test
public void addingARecipeShouldMakeItAvailableInRandomRecipes() {
// your test
}
// Inject the recipeRepository mock in the context
#Bean
RecipesRepository recipesRepository() {
return Mockito.mock(RecipesRepository.class);
}
}

It sounds like you are trying to rely on auto-wiring so that Spring correctly injects the RecipesRepository instance as a dependency into RecipesApplication. This question seems to be similar to yours. That being said, I'm hesitant to declare a duplicate because you may not have make use of something like [ReflectionTestUtils][2]. Rather, try just deleting the recipesRepository field in RecipesApplicationTest, keeping recipesRepositoryMock field. But, before the dto = recipesApplication.createRecipe(dto) line, try to see if you can manually inject the recipesRepositoryMock into recipesApplication via a call like recipesApplication.setRecipesRepository(recipesRepositoryMock) or something similar.

Related

Why tested class based autowire annotation throw null exception?

I use Spring Boot 5 and JUnit in my project. I create a unit test to test the service.
Here is the service that I am testing:
#Service
#RequiredArgsConstructor
#Slf4j
public class BuilderServiceImpl implements BuilderService{
#Autowired
public AutoMapper autoMapper;
private final BuilderRepository builderRepository;
private final AdminUserRepository adminUserRepository;
#Override
public BuilderDto getByEmail(String email){
}
#Override
public List<BuilderMinDto> getAll() {}
#Override
public List<BuilderMinDto> getAll(int page, int size) {}
#Override
public SaveBuilderResponse create(Builder builder){
var str = autoMapper.getDummyText();
Builder savedBuilder = builderRepository.save(builder);
return new SaveBuilderResponse(savedBuilder);
}
}
And here is the test class that tests the service above:
#SpringBootTest
#RequiredArgsConstructor
#Slf4j
class BuilderServiceImplTest {
#Mock
private BuilderRepository builderRepository;
#Mock
private AdminUserRepository adminUserRepository;
private AutoCloseable autoCloseable;
private BuilderService underTest;
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
#AfterEach
void tearDown () throws Exception{
autoCloseable.close();
}
#Test
void getByEmail(){}
#Test
#Disabled
void getAll() { }
#Test
#Disabled
void testGetAll() {}
#Test
void create() {
//given
Builder builder = new Builder();
builder.setName("John Johnson");
builder.setCompanyName("Builders Test");
builder.setEmail("test#builders.com");
//when
underTest.create(builder);
//then
ArgumentCaptor<Builder> builderArgumentCaptor = ArgumentCaptor.forClass(Builder.class);
verify(builderRepository)
.save(builderArgumentCaptor.capture());
Builder captureBuilder = builderArgumentCaptor.getValue();
assertThat(captureBuilder).isEqualTo(builder);
}
}
When I start to run the test class the create method in BuilderServiceImpl fired and on this row:
var str = autoMapper.getDummyText();
I get NullPointerException(autoMapper instance is null).
Here is the definition of AutoMapper class:
#Component
#Slf4j
#RequiredArgsConstructor
public class AutoMapper {
public String getDummyText(){
return "Hello From AutoMapper.";
}
}
As you can see I use #Component annotation to register the AutoMapper class to the IoC container and Autowired annotation to inject it into autoMapper property in BuilderServiceImpl class.
Why autoMapper instance is null? How can I make autoMapper to be initialized?
In order to make #Autowire work you have to use the instance of BuilderServiceImpl (object under test) created by spring itself.
When you create the object like this (by yourself, manually):
#BeforeEach
void setUp(){
....
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
Spring doesn't know anything about this object, hence Autowiring won't work
Another thing that might be useful:
You've used #Mock for BuilderRepository and AdminUserRepository.
This are plain mockito annotation, and if you're using an integration/system test that runs the spring under the hood, probably this is not what you want:
Surely, it will create a mock, but it won't put it onto an application context, and won't substitute the beans of these classes that might have been created by spring.
So if this is what you want to achieve, you should use #MockBean instead.
This annotation belongs to Spring Testing framework rather than a plain mockito annotation.
All-in-all you might end up with something like this:
#SpringBootTest
class MyTest
{
#MockBean
BuilderRepository builderRepo;
#MockBean
AdminUserRepository adminUserRepo;
#Autowired // spring will inject your mock repository implementations
// automatically
BuilderServiceImpl underTest;
#Test
void mytest() {
...
}
}
Add #Autowire annotation Annotations on below fields. Error due your not initialized below object In BuilderServiceImpl
#Autowire
private final BuilderRepository builderRepository;
#Autowire
private final AdminUserRepository adminUserRepository;
Why are you creating BuildService manually? If you do this, set AutoMapper manualy too.
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
underTest.setAutoMapper(new AutoMapper());
}
You are not using di.

Initiate object via constructor through #AutoWired during runtime

I was new to Springboot application using the #Autowired to perform the dependency injection. We can use #Autowired directly by initiate that class object for class that has none or default parameterless constructor. But what if a class has some parameter in its constructor, and I would like to initiate it during runtime conditionally and automatically, is it possible to do that?
For example
#Component
public class SomeContext {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Component
public class SomeBuilder {
private final SomeContext ctx;
#Autowired
public SomeBuilder(SomeContext ctx) {
this.ctx = ctx;
}
public void test() {
System.out.println("ctx name: " + ctx.getName());
}
}
#Service
public class SomeService {
#Autowired
SomeBuilder someBuilder;
public void run(SomeContext ctx) {
if (ctx != null) {
// I want someBuilder to be initiated here in someway with my input ctx
// but NOT doing through new with constructor like below
// someBuilder = new SomeBuilder(ctx);
someBuilder.test(); // ctx name: null, I would expect to see "ctx name: someUser", while ctx was injected into someBuilder in any possible way
}
}
}
#RestController
public class HelloWorldController
{
#Autowired
SomeService someService;
#RequestMapping("/")
public String hello() {
SomeContext someContext = new SomeContext();
someContext.setName("someUser");
someService.run(someContext);
return "Hello springboot";
}
}
I'm not sure I've got your question right, but from the code it looks like you really want to create a new instance of SomeBuilder every time you call the run method of SomeService.
If so, I think the first thing to understand is that in general the injection magic happens only if the class is managed by Spring by itself. Read, if spring creates the object of the class - it will inject stuff into it otherwise you're on your own here.
The next thing to understand is that, if you have a object of class SomeBuilder managed by spring and you want to inject SomeContext into it, this SomeContext instance has to be managed by spring as well.
Bottom line, spring can deal only with objects that it manages. These objects are stored in a 'global registry' of all the objects called ApplicationContext in spring.
Now Spring has a concept of prototype scope vs. singleton scope. By Default all the beans are singletons, however you can easily alter this behavior. This has two interesting consequences:
You Can create prototype objects being injected into the singleton upon each invocatino (of method run in your case, so the SomeBuilder can and I believe should be a prototype)
Prototype objects are not stored in the application contexts so the capabilities of injecting stuff in there during the runtime are rather limited
With all this in mind:
If you want to create SomeContext like you do in the controller, its not managed by spring, so you can't use Injection of spring as is into the builder.
The builder is a singleton, so if you inject it with a regular #Autowire into another singleton (SomeService in your case), you'll have to deal with the same instance of the builder object - think about concurrent access to the method run of SomeService and you'll understand that this solution is not really a good one.
So these are the "inaccuracies" in the presented solution.
Now, in terms of solution, you can:
Option 1
Don't manage builders in Spring, not everything should be managed by spring, in this case you'll keep your code as it is now.
Option 2
and this is a the solution, although pretty advanced one:
Use Java Configuration to create prototype beans in runtime with an ability to inject parameters into the bean.
Here is an example:
// Note, I've removed all the annotations, I'll use java configurations for that, read below...
public class SomeBuilder {
private final SomeContext ctx;
public SomeBuilder(SomeContext ctx) {
this.ctx = ctx;
}
public void test() {
System.out.println("ctx name: " + ctx.getName());
}
}
Now the class SomeService will also slightly change:
public class SomeService {
private Function<SomeContext, SomeBuilder> builderFactory;
public void run(SomeContext ctx) {
SomeBuilder someBuilder = builderFactory.apply(ctx);
someBuilder.test();
}
}
And now you should "glue" it to spring in an advanced way with Java Configurations:
#Configuration
public class MyConfiguration {
#Bean
public Function<SomeContext, SomeBuilder> builderFactory() {
return ctx -> someBuilder(ctx);
}
#Bean
#Scope(value = "prototype")
public SomeBuilder someBuilder(SomeContext ctx) {
return new SomeBuilder(ctx);
}
#Bean
public SomeService someService() {
return new SomeService(builderFactory());
}
}
For more details with a really similar example, see this tutorial

unit test Spring boot service class that has dependency on another service class

I am writing up a spring boot rest service in which am trying to unit test a class annotated with #Service.
This service class internally uses another service class.
Here is the code :
#Service
public class TieredClaimServiceImpl implements TieredClaimService {
//this is the second service used within
// commented out setter injection and used constructor injection
// #Autowired
private DiscountTierService discountTierService;
#Autowired
public TieredClaimServiceImpl(MerchRepository merchRepository,SalesRepository
salesRepository,DiscountTierService discountTierService) {
this.merchRepository = merchRepository;
this.salesRepository = salesRepository;
this.discountTierService = discountTierService;
}
Here is the method within the class that I would need to unit test :
#Override
public List <TieredClaimDto> calculateClaim(ClaimRequestDto claimRequestDto,String xAppCorelationId) throws SystemException {
/** get the discount tier config data - this is where we are using the other service **/
List<DiscountTierDto> discountTierList = discountTierService.get();
I would like to mock the 'DiscountTierService' used within 'TieredClaimServiceImpl'
In my unit test class I tried to mock the call being made to this service :
DiscountTierService discountTierService = mock(DiscountTierService.class);
OR
DiscountTierService discountTierService = spy(new DiscountTierServiceImpl());
Neither of these worked .
Though not directly related I had a question related to this entire solutioning here
You are saying about unit tests but trying to create the integration test
#SpringBootTest
#ActiveProfules(value = "test")//or #TestPropertySource(s)
#RunWith(value = SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {SomeTestConfiguration.class})
public class SomeTestClass {
#Autowired//or #Mock
private MerchRepository merchRepository;
#Autowired//or #Mock
private SalesRepository salesRepository;
#Mock
private DiscountTierService discountTierService;
private TieredClaimService service;
#Before
public void setup() {
service = new TieredClaimServiceImpl(merchRepository, salesRepository, discountTierService);
}
#Test
public void test() {
//arrange
var dto1 = new DiscountTierDto(...);
var dto2 = new DiscountTierDto(...);
var someList = List.of(dto1, dto2);
when(discountTierService.get()).thenReturn(someList);
//act
service.calculateClaim(someClaimRequestDto, someAppCorrelationId);
//assert
Assert.assertThat(...);
}
}
If you want really to create a unit test, you don't need most class annotations, just #RunWith, and set test properties if necessary (of course, in this case, you cannot autowire repositories, just mock).
But if you are looking on the integration test where you will call a controller method which calls the service, you need to create MockMvc object with a standalone controller. And creating the controller object just set this service configuration - in this case, you can control all required nested objects (service, nested service, repositories).

Injecting mock before Spring's post-construct phase

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.

JUnit Rule using a spring bean

I have a test class which loads a test spring application context, now I want to create a junit rule which will setup some test data in mongo db. For this I created a rule class.
public class MongoRule<T> extends ExternalResource {
private MongoOperations mongoOperations;
private final String collectionName;
private final String file;
public MongoRule(MongoOperations mongoOperations, String file, String collectionName) {
this.mongoOperations = mongoOperations;
this.file = file;
this.collectionName = collectionName;
}
#Override
protected void before() throws Throwable {
String entitiesStr = FileUtils.getFileAsString(file);
List<T> entities = new ObjectMapper().readValue(entitiesStr, new TypeReference<List<T>>() {
});
entities.forEach((t) -> {
mongoOperations.save(t, collectionName);
});
}
}
Now I am using this rule inside my test class and passing the mongoOperations bean.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SpringTestConfiguration.class)
public class TransactionResourceTest {
#Autowired
private ITransactionResource transactionResource;
#Autowired
private MongoOperations mongoOperations;
#Rule
public MongoRule<PaymentInstrument> paymentInstrumentMongoRule
= new MongoRule(mongoOperations, "paymentInstrument.js", "paymentInstrument");
....
}
The problem is that Rule is getting executed before application context gets loaded, so mongoOperations reference is passed as null. Is there a way to make rules run after the context is loaded?
As far as I know what you are trying to achieve is not possible in such straight forward way because:
the rule is instantiated prior Spring's Application Context.
SpringJUnit4ClassRunner will not attempt to inject anything on the rule's instance.
There is an alternative described here: https://blog.jayway.com/2014/12/07/junit-rule-spring-caches/ but I think it would fall short in terms of what can be loaded into mongodb.
In order to achieve what you want to achieve, you would probably require a test execution listener that would inject whatever dependencies you require on your rule object.
Here's a solution, using some abstract super class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SpringTestConfiguration.class)
public abstract class AbstractTransactionResourceTest<T> {
#Autowired
private ITransactionResource transactionResource;
#Autowired
private MongoOperations mongoOperations;
#Before
public void setUpDb() {
String entitiesStr = FileUtils.getFileAsString(entityName() + ".js");
List<T> entities = new ObjectMapper().readValue(entitiesStr, new TypeReference<List<T>>() {});
entities.forEach((t) -> {
mongoOperations.save(t, entityName());
});
}
protected abstract String entityName();
}
then
public class TransactionResourceTest extends AbstractTransactionResourceTest<PaymentInstrument> {
#Override
protected String entityName() {
return "paymentInstrument";
};
// ...
}

Categories