JUnit Rule using a spring bean - java

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";
};
// ...
}

Related

How to write a test for a service in which beans self-register

Problem with correct class setting for tests.
I have the following service structure
My service:
Interface
public interface ColumnsFromTableService {
List<ColumnsDto> getTableColumnsFromSource(DataProvider dataProvider, String tableName);
DataProviderSourceType myDataProviderSourceType();
#Autowired
default void regMe(ColumnsFromTableFacade columnsFromTableFacade){
columnsFromTableFacade.register(myDataProviderSourceType(),this);
}
}
Impl
#Service
#RequiredArgsConstructor
public class OracleColumnsFromTableServiceImpl implements ColumnsFromTableService {
private final DataProviderInsideDao dataProviderInsideDao;
#Override
public List<ColumnsDto> getTableColumnsFromSource(DataProvider dataProvider, String tableName) {
return dataProviderInsideDao.getColumnsByTableNameFromOracle(dataProvider, tableName);
}
#Override
public DataProviderSourceType myDataProviderSourceType() {
return DataProviderSourceType.ORACLE;
}
}
My facade:
Interface
public interface ColumnsFromTableFacade {
List<ColumnsDto> getTableColumnsFromSource(DataProvider dataProvider, String tableName);
void register(DataProviderSourceType dataProviderSourceType, ColumnsFromTableService columnsDataProviderService);
}
Impl
#Service
public class ColumnsFromTableFacadeImpl implements ColumnsFromTableFacade {
private final Map<DataProviderSourceType, ColumnsFromTableService> implementationMap = new HashMap<>();
#Override
public List<ColumnsDto> getTableColumnsFromSource(DataProvider dataProvider, String tableName) {
ColumnsFromTableService columnsFromTableService = implementationMap.get(dataProvider.getSourceType());
return columnsFromTableService.getTableColumnsFromSource(dataProvider,tableName);
}
#Override
public void register(DataProviderSourceType dataProviderSourceType, ColumnsFromTableService columnsDataProviderService) {
implementationMap.put(dataProviderSourceType, columnsDataProviderService);
}
}
For use, I inject the facade in the place I need.
Everything works in the application. When creating ColumnsFromTableService beans, Spring Boot sees the #Autowired annotation in the interface and and registers the service in the facade. But when testing this facade, I can't set it up correctly.
My test:
#ExtendWith(MockitoExtension.class)
public class EasyServiceTest {
#InjectMocks
TablesFromSourceFacadeImpl tablesFromSourceFacade;
#Test
void test(){
tablesFromSourceFacade.getAllTablesFromSource(new DataProvider());
}
}
When running the test, the facade is successfully instantiated. But the collection with implementations is empty.
enter image description here
I am using
Junit jupiter - 5.7.1
Spring boot - 2.4.3
I decided to be rough
#ExtendWith(MockitoExtension.class)
public class EasyServiceTest {
TablesFromSourceFacadeImpl tablesFromSourceFacade;
#InjectMocks
OracleTablesFromSourceServiceImpl oracleTablesFromSourceService;
#InjectMocks
OracleColumnsFromTableServiceImpl oracleColumnsFromTableService;
#BeforeEach
void setUp() {
tablesFromSourceFacade = new TablesFromSourceFacadeImpl();
tablesFromSourceFacade.register(postgresTablesFromSourceService.myDataProviderSourceType(),
postgresTablesFromSourceService);
tablesFromSourceFacade.register(oracleTablesFromSourceService.myDataProviderSourceType(),
oracleTablesFromSourceService);
}
#Test
void test(){
tablesFromSourceFacade.getAllTablesFromSource(new DataProvider());
}
}
UPDATED
The second solution to the problem is to raise either the entire context of the spring, or part of it. But in my case, this did not work, since the services are scattered across different packages, and I would have to raise the entire context, which is heavy.

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.

Java Spring Test Autowired Controller is null error

I would like to test a Java Web Controller using Spring (Not Spring Boot).
My Controller is
#Controller
#RequestMapping("/orders")
public class OrderHdrController {
#RequestMapping(value = "/getOrderList", method = RequestMethod.POST)
#ResponseBody
public Map<String, Object> getOrderTables(OrderSearchDto orderSearchDto) { ... }
}
And my test class is:
public class FilterActivityTest2 {
#Autowired
private OrderHdrController orderHdrController;
#Test
public void testActivity() {
OrderSearchDto orderSearchDto = new OrderSearchDto();
OrderSearchPanelDto orderSearchPanelDto = new OrderSearchPanelDto();
orderSearchPanelDto.setActivityTypes(Arrays.asList("TAKEOVER","DELIVERY"));
orderSearchDto.setOrderSearchPanelDto(orderSearchPanelDto);
Map<String, Object> result = orderHdrController.getOrderTables(orderSearchDto);
assertNotNull(result);
}
}
I do not want to mock any objects. I just want to run the test on the controller all the way to the db. But when I debug into the test, the orderHdrController is null in testActivity method.
What have I done wrong? Please help or ask me for more information. Thanks.
#RunWith(SpringRunner.class)
#WebMvcTest(OrderHdrController.class)
public class FilterActivityTest2 {
#Autowired
private OrderHdrController orderHdrController;
#Test
public void testActivity() {
OrderSearchDto orderSearchDto = new OrderSearchDto();
OrderSearchPanelDto orderSearchPanelDto = new OrderSearchPanelDto();
orderSearchPanelDto.setActivityTypes(Arrays.asList("TAKEOVER","DELIVERY"));
orderSearchDto.setOrderSearchPanelDto(orderSearchPanelDto);
Map<String, Object> result = orderHdrController.getOrderTables(orderSearchDto);
assertNotNull(result);
}
}
or If dont using any spring or junit then why using #test
simply make a main class
public class FilterActivityTest2{
public static void main(String args[]){
....... put your tast case code here
}
}
Your FilterActivityTest2 needs to be managed by the Spring context, to be able to autowire dependencies.
To do that, either annotate your test class with:
#RunWith(SpringRunner.class)
#SpringBootTest
Or extend the main test class that already has these annotations. If you created your project using spring initializer, you'll find that class in the tests created for you.
public class FilterActivityTest2 extends MyApplicationTests {
EDIT
For Spring, you can use #ContextConfiguration. Here is a good tutorial.
Also see the official documentation here.
If you do not use spring boot then you can create the application context manually
#Before
public void init() {
ApplicationContext context = desired implementation;
controller = context.getBean("bean name");
}
But better do this
#RunWith(MockitoJUnitRunner.class)
public class FilterActivityTest2 {
private OrderHdrController orderHdrController;
#MockBean
private Service service;
#MockBean
private Dao dao;
#Before
public void init() {
orderHdrController = new OrderHdrController(service, dao ....);
}
#Test
....
}

Spring Boot: how to inject dependencies into a class called by a library?

I'm using Kinesis Client Library (KCL) and Spring boot. To use KCL, I have to implement a class (I named it RecordProcessor) for interface IRecordProcessor. And KCL will call this class and process records from kinesis. But when I tried to use dependency injection, I found it was not succeeded.
Here's the snippet for RecordProcessor:
#Component
public class RecordProcessor implements IRecordProcessor {
#Autowired
private SingleRecordProcessor singleRecordProcessor;
#Override
public void initialize(String shardId) {
...
}
#Override
public void processRecords(List<Record> records, IRecordProcessorCheckpointer checkpointer) {
...
}
}
I use Class SingleRecordProcessor to process single each record from kinesis. And this is my SingleRecordProcessor class snippet:
#Component
public class SingleRecordProcessor {
private Parser parser;
private Map<String, Table> tables;
public SingleRecordProcessor() {
}
#Autowired
private void setParser(Parser parser) {
this.parser = parser;
}
#Autowired
private void setTables(Map<String, Table> tables) {
this.tables = tables;
}
public void process(String record) {
...
}
}
I want to let spring framework automatically inject the SingleRecordProcessor instance into the class and use it. But I found that the field singleRecordProcessor is null.
Any idea why the dependency injection is failed? Or is it impossible to inject dependencies into a class which is called by other framework (in this case it's KCL)? Any suggestions will be appreciated! Really need some help please!!
[UPDATE]:
Sorry for not expressing the error clearly. The error was NullPointerException. I tried to inject singleRecordProcessor and call method process() on it. I think the injection was not successful so the instance singleRecordProcessor is null and there comes the NullPointerException.
More information is as follows:
I have a major class called Application
#SpringBootApplication
public class Application{
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./app.pid"));
ConfigurableApplicationContext ctx = application.run(args);
}
}
And I have the MainProcessor class which will call KCL.
#Service
public final class MainProcessor {
#EventListener(ApplicationReadyEvent.class)
public static void startConsumer() throws Exception {
init();
IRecordProcessorFactory recordProcessorFactory = new RecordProcessorFactory();
Worker worker = new Worker(recordProcessorFactory, kinesisClientLibConfiguration);
...
worker.run(); // this line will call KCL library and eventually call ProcessorRecord class.
}
}
[UPDATE2]
RecordProcessorFactory only has one method like this
#Component
public class RecordProcessorFactory implements IRecordProcessorFactory {
#Autowired
RecordProcessor recordProcessor;
#Override
public IRecordProcessor createProcessor() {
return recordProcessor;
}
}
It creates a new RecordProcessor instance for KCL to use it.
You should autowire an instance of this into your MainProcessor:
#Component
public class RecordProcessorFactory {
#Lookup IRecordProcessor createProcessor() { return null; }
}
Spring will instantiate a RecordProcessorFactory for you, and replace the implementation of createProcessor() in it with one that will return a new IRecordProcessor each time it's called. Both the factory and the processors will be Spring beans - which is what you want.

Injecting a mock in Spring application context creates a copy

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.

Categories