Load properties when MockitoJUnitRunner is used - java

I'm trying to load a property value from my application-test.properties in my service unit tests(using MockitoJUnitRunner.class) but it gets always a null value using #Value annotation.
#RunWith(MockitoJUnitRunner.class)
public class AlertQueryServiceImplTest {
#Value("${raw.path}")
private static String rawJson;
#InjectMocks
private AlertQueryServiceImpl alertQueryServiceImpl;
#Mock
private AlertCBDAOImpl alertCBDAOImpl;
private List<Alert> alertListExpected;
#Before
public void setUp() {
ReflectionTestUtils.setField(alertQueryServiceImpl, "url", "http://someurl");
alertListExpected = JsonUtils.
loadObjectList(Alert.class, JsonUtils.ALERTS_FILE);
assertFalse(alertListExpected.isEmpty());
}
#Test
public void shouldReturnAlerts() {
User userTest = new User("108998", "3000747091");
JsonDocument jsonDocExpected = JsonUtils.stringToJsonDocument(rawJson, userTest.getAssociatedBE());
when(alertCBDAOImpl.getDoc(userTest.getAssociatedBE())).thenReturn(jsonDocExpected);
when(alertCBDAOImpl.getAlerts(jsonDocExpected)).thenReturn(alertListExpected);
}
}
How could I load raw.path property into rawJson field?

First you need to set active profile to access application-test.properties file you may do that using #ActiveProfiles("test") annotation on your test class.
https://www.baeldung.com/spring-testing-separate-data-source
I don't think #Value annotation will work with static keyword.

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.

Java/Spring: initMocks a class which got #Value from yml file

I am writing unit tests on a Service class like below :
#Component
#Profile({"default", "dev"})
public class MyService {
#Value("${my.property}")
private String property;
private OtherService otherService;
public void MyMethod() {
String myVar = otherService.method();
return String.format("MyVar %s & MyProperty %s", myVar, property);
}
}
My current test class for this test is like this:
#ActiveProfiles("dev")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {
#Mock
private OtherService otherService;
#InjectMocks
private MyService myService;
#BeforeEach()
public void init() {
MockitoAnnotations.initMocks(this);
when(otherService.method()).thenReturn("a string");
}
#Test
public void shouldReturnException() {
final Exception exception = assertThrows(ApiErrorException.class,
() -> myService.myMethod(var));
assertThat(exception).hasMessage("here the message");
verify(otherService, never()).method();
}
}
With this two classes, I have an application.yml & application-dev.yml to set my.property.
I want to get the property from the application-dev file during my tests execution.
But, with #InjectMocks, property is null. Whereas, using #Autowired in place of/with #InjectMocks, the property variable is set with the value present in file.
Problem, using Autowired with/in place of InjectMock results in the otherService variable being initialized, so no mock is created.
How can I still use Mockito, while having the property variable set with the value in the file?
I saw about ReflectionTestUtils.setField, but using it mean having no use of a yml file (which i am not fan).
Have a nice day
With the help of #Deadpool, the tests can use the values written in the application.yml file.
But, using #MockBean and #Autowired, the tests get a behavior I do not understand.
Example:
I test that a method return an Exception, and I verify that others methods are not called after the exception was catch: verify(otherService, never()).otherMethod();
Writing this line returns the following error org.mockito.exceptions.verification.NeverWantedButInvoked.
The initial exception is correctly caught, but the test does not seem to acknowledge that no other services must be called.
#SpringBootTest is used for integration testing which loads the ApplicationContext that will be utilized for test environment
The #SpringBootTest annotation can be used when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests.
Since you are generating integration environment using #SpringBootTest you need to mock the bean using #MockBean annotation
#ActiveProfiles("dev")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {
#MockBean
private OtherService otherService;
#Autowire
private MyService myService;
#BeforeEach
public void init() {
when(otherService.method()).thenReturn("a string");
}
}
I suggest you change your MyService class to accept the OtherService either via the constructor or via setter. Something like this:
#Component
#Profile({"default", "dev"})
public class MyService {
#Value("${my.property}")
private String property;
private OtherService otherService;
public MyService(OtherService otherService) {
this.otherService = otherService
}
public void MyMethod() {
String myVar = otherService.method();
return String.format("MyVar %s & MyProperty %s", myVar, property);
}
}
And then you do your test like this:
#ActiveProfiles("dev")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApplication.class)
public class MyServiceTest {
#Mock
private OtherService otherService;
#InjectMocks
#Autowired
private MyService myService;
#BeforeEach()
public void init() {
MockitoAnnotations.initMocks(this);
when(otherService.method()).thenReturn("a string");
}
}

Initialize Mocks in test before #BeforeStep

I have a custom reader with an #BeforeStep function in order to initialize some data. These data are comming from an external database.
#Component
public class CustomReader implements ItemReader<SomeDTO> {
private RestApiService restApiService;
private SomeDTO someDTO;
#BeforeStep
private void initialize() {
someDTO = restApiService.getData();
}
#Override
public SomeDTO read() {
...
return someDTO
}
}
In my unit test i need to mock the calls to the external database.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = NedBatchApplication.class)
public class CustomReaderTest {
#Autowired
CustomReader customReader;
#Mock
RestApiService restApiService;
#Before
private void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(customReader, "restApiService", restApiService);
Mockito.when(restApiService.getData().thenReturn(expectedData);
}
}
The problem i am facing is the #BeforeStep is executed before the #Before from the unit test, when i lauch my Test. So restApiService.getData() returns null instead of expectedData.
Is there a way to achieve what i want or do i need to do it with a different approach ?
After some reflexion with a co-worker he gave me a solution :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = NedBatchApplication.class)
public class CustomReaderTest {
CustomReader customReader;
#Mock
RestApiService restApiService;
#Before
private void setup() {
MockitoAnnotations.initMocks(this);
Mockito.when(restApiService.getData().thenReturn(expectedData);
this.customReader = new CustomReader(restApiService);
}
#Test
public void test() {
customReader.initialize();
(...)
}
}
Are you certain that the BeforeStep is running before the Before annotation (by using logging or similar?).
It's possible your Mockito invocation is not fully correct. Try using Mockito.doReturn(expectedData).when(restApiService).getData() instead.
As an alternative approach, if the RestApiService was autowired in your custom reader, you'd be able to use the #InjectMocks annotation on the custom reader declaration in your test, which would cause the mocked version of your restApiService to be injected to the class during the test.
Usually when using Spring based tests, try to make dependencies like restApiService (the ones you would like to mock) to be spring beans, and then you can instruct spring to create mock and inject into application context during the application context creation with the help of #MockBean annotation:
import org.springframework.boot.test.mock.mockito.MockBean;
#RunWith(SpringRunner.class)
#SpringBootTest(classes = NedBatchApplication.class)
public class CustomReaderTest {
#MockBean
private RestApiService restApiService;
}

#TestPropertySource - Values not set / set as null from test properties file

My Junit is not picking up properties set in test properties file.
I get no error, but value returned from properties file is null
CLASS TO BE TESTED:
package com.abc.mysource.mypackage;
#Component
#ComponentScan
public class MyHelper {
#Autowired
#Qualifier("commonProperties")
private CommonProperties commonProperties;
public LocalDateTime method1ThatUsesCommonProperties(LocalDateTime startDateTime) throws Exception {
String customUserType = commonProperties.getUserType(); // Returns null if run as JUnit test
//Further processing
}
}
SUPPORTING COMPONENTS - BEANS & CONFIG CLASSES:
package com.abc.mysource.mypackage;
#Component
public class CommonProperties {
#Value("${myhelper.userType}")
private String userType;
public String getUserType() {
return userType;
}
public void setCalendarType(String userType) {
this.userType = userType;
}
}
CONFIG CLASS:
package com.abc.mysource.mypackage;
#Configuration
#ComponentScan(basePackages ="com.abc.mysource.mypackage.*")
#PropertySource("classpath:default.properties")
public class CommonConfig {}
default.properties under src/main/resources
myhelper.userType=PRIORITY
MY TEST CLASS:
package com.abc.mysource.mypackage.test;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes=MyHelper.class)
#TestPropertySource("classpath:default-test.properties")
#EnableConfigurationProperties
public class MyHelperTest {
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
#Autowired
private MyHelper myHelper;
#Test
public void testMethod1ThatUsesCommonProperties() {
myHelper.method1ThatUsesCommonProperties();
}
}
default-test.properties defined under /src/test/resources:
myhelper.userType=COMMON
NOTE:
I moved default-test.properties to /src/main/resources - commonProperties.getUserType() is null
I even used #TestPropertySource(properties = {"myhelper.userType=COMMON"}). Same result
NOTE 2:
I tried the solution on #TestPropertySource is not loading properties.
This solution requires me to create a duplicate bean called CommonProperties under src/test/java. But #MockBean fails when I do
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
Please do not mark a duplicate.
NOTE 3:
Mine is a spring, not a spring boot application.
MockBeans are suited if you don't need specific state. Usually this bean is "isolated" and every method call of this bean will have the same result. It is "isolated" -> the service that uses #Value annotation will not apply on this bean.
What you need is a "normal" bean, properly constructed and initialized. Please use #Autowired annotation and define another bean if needed, using a test profile.

Providing context for #Value fields from a unit test

I have a class like this:
#Service("someClient")
public class SomeClient {
#Value{some.value}
private String someValue;
public void someMethod() {
return someValue;
}
}
And a test like this:
#ContextConfiguration(locations = "classpath:/some/where/testApplicationContext.xml")
#RunWith(SpringJUnit4ClassRunner.class)
public class SomeClientTest extends TestCase {
#Value{some.value}
private String someValueTest;
#Test
public void shouldWork() {
...
someClient.someMethod()
...
}
}
When the wider application is running, the field someValue inside the SomeClient class is populated from a properties file referenced from testApplicationContext.xml. When I run the test in debug mode I can see that someValueTest is populated in the test, but when the test calls the class under test, the value is not populated.
I could use some advice! Obviously I can change the visibility of the field in the class, or provide a setter, however I would like to avoid that if possible. If it isn't, please advise.
In order to populate fields with #Value annotation in your test you need to configure PropertySourcesPlaceholderConfigurer.
Add the following to your test:
#Configuration
public static class Config {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
To read the values from test property file you can add
#TestPropertySource(locations="classpath:test.properties") to your Test class declaration
You can use ReflectionTestUtils from org.springframework.test.util.ReflectionTestUtils package to mock any variable, including the ones that access the properties file.
#RunWith(SpringJUnit4ClassRunner.class)
public class SomeClientTest extends TestCase {
private SomeClient someClient;
#Test
public void shouldWork() {
//Initialize someClient
someClient = new SomeClient();
ReflectionTestUtils.setField(someClient, "variable name", "the variable value");
someClient.someMethod()
...
}
}

Categories