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");
}
}
Related
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.
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;
}
Running a test class throws the following exception:
BeanNotOfRequiredTypeException: Bean named 'myServiceImpl' is expected to be of type 'MyServiceImpl' but was actually of type 'com.sun.proxy.$Proxy139'
This error gets thrown only with unit tests, the program itself works.
My Interface
public interface MyService {
public String testMethod();
}
My Implementation
#Service
public class MyServiceImpl implements MyService{
#Autowired
private TransactionRepository transactionRepo;
#Autowired
private AccountRepository accountRepository;
#Autowired
private BankAccountStatementFactory baStatementFactory;
public String myMethod() {
return "Run My Method";
}
}
My Unit Test
#RunWith(SpringRunner.class)
#SpringBootTest
public class DeleteMeTest{
#Mock
private TransactionRepository transactionRepo;
#Mock
private AccountRepository accountRepository;
#Mock
private BankAccountStatementFactory baStatementFactory;
#InjectMocks
#Resource
MyServiceImpl myService;
#org.junit.Before
public void setUp() throws Exception {
// Initialize mocks created above
MockitoAnnotations.initMocks(this);
}
#Test
public void test() {
myService.myMethod();
System.out.println("My Unit Test");
}
}
Running this test class throws the following exception:
BeanNotOfRequiredTypeException: Bean named 'myServiceImpl' is expected to be of type 'MyServiceImpl' but was actually of type 'com.sun.proxy.$Proxy139'
A solution here is to inject the Interface, not the implementation into the unit test but this will not allow me to inject mocks.
Thats because an implementation is required with the #InjectMocks annotation. When I try to inject mocks into the interface I get the following exception:
Cannot instantiate #InjectMocks field named 'myService'! Cause: the type 'MyService' is an interface.
Just to be clear, all this worked in the beginning and went bad once I repackaged my classes. That could be the cause but not 100% sure.
Any hints on what might cause this BeanNotOfRequiredTypeException?
Thanks!
Since it's a unit test, you don't need Spring IMHO.
Simply initialize the tested class this way:
#InjectMocks
MyServiceImpl myService = new MyServiceImpl();
You can also remove these annotations:
#RunWith(SpringRunner.class)
#SpringBootTest
If you really need to use Spring (the reason for using Spring in the unit test is not clear from your post), you can try to unproxy the bean:
Have a separate declaration for the proxy and the bean:
#Resource
MyServiceImpl proxy;
#InjectMocks
MyServiceImpl myService;
Then initialize them in setUp():
#org.junit.Before
public void setUp() throws Exception {
// Initialize mocks created above
myService = (MyServiceImpl)((TargetSource) proxy).getTarget();
MockitoAnnotations.initMocks(this);
}
I have a Spring boot service defined like this
#Service
public class MyService {
private String field1;
private String field2;
#Autowired
private AnotherService anotherService
#PostConstruct
public void init() {
anotherService.initField1(field1);
anotherService.initField2(field2);
}
public String foo() {
return field1 + field2;
}
}
How should I write a unit test for foo. Well, it's more about how to deal with class fields and the PostConstruct methods.
Thanks!!
EDIT:
Added AnotherService as a field as well.
The following example shows a #Service Bean that uses constructor injection to obtain a required AnotherService bean:
#Service
public class MyService {
private String field1;
private String field2;
private final AnotherService anotherService;
public MyService(AnotherService anotherService) {
this.anotherService = anotherService;
this.anotherService.initField1(field1);
this.anotherService.initField2(field2);
}
public String foo() {
return field1 + field2;
}
}
Note you can omit the #Autowired becuase MyService has one constructor. See here for more info.
testing with Spring
Use the #RunWith(SpringRunner.class) and #SpringBootTest to inject MyService and start using it:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyServiceTest {
#Autowired
private MyService service;
#Test
public void testFoo() {
String expResult = "";
String result = service.foo();
assertEquals(expResult, result);
}
}
testing without Spring
public class MyServiceTest2 {
private MyService service;
#Before
public void setUp() {
service = new MyService(new AnotherService.Fake());
}
#Test
public void testFoo() {
String expResult = "";
String result = service.foo();
assertEquals(expResult, result);
}
}
Here Fake is a fake implementation of the AnotherService interface which allows you to have a pure unit test.
Writing good, testable code can be hard. There are some pitfalls waiting for everyone to fall into sooner or later.
As a rule of thumb, try to avoid field level injection, use constructor parameter injection instead:
#Service
public class MyService {
private AnotherService anotherService;
#Autowired
MyService (AnotherService anotherService) {
this.anotherService = anotherService;
}
}
This is the cleanest solution. You can call the constructor from your tests, spring will inject dependencies the same way at runtime. So there is no difference to deal with.
The same goes for any life cycle constructs like #PostConstruct. If you can avoid them, do it. Let the constructor handle it. If you absolutely have to keep them around, well, the only logical solution is to manually call them from your test code.
Now, how to setup services that at runtime would be autowired by the container?
For unit testing, you basically have three options (in no particular order):
If the required service is rather simple and can easily be constructed, create and pass it as the framework would do.
If the service has a limited interface that does not change too often, create a fake service.
Use a mocking lib like mockito (spring-boot-test provides it by default).
I'm using an autowired constructor in a service that when instantiated in the test class causes the #Value annotations to return null. Autowiring the dependencies directly solves the problem but the project follows the convention of using constructor based autowiring. My understanding is that instantiating the service in the test class is not creating it from the Spring IoC container which causes #Value to return null. Is there a way to create the service from the IoC container using constructor based autowiring without having to directly access the application context?
Example Service:
#Component
public class UpdateService {
#Value("${update.success.table}")
private String successTable;
#Value("${update.failed.table}")
private String failedTable;
private UserService userService
#Autowired
public UpdateService(UserService userService) {
this.userService = userService;
}
}
Example Test Service:
#RunWith(SpringJUnite4ClassRunner.class)
#SpringApplicationConfiguration(classes = {TestApplication.class})
#WebAppConfiguration
public class UpdateServiceTest {
private UpdateService updateService;
#Mock
private UserService mockUserService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
updateService = new UpdateService(mockUserService);
}
}
To make #Value work updateService should be inside of spring context.
The best practice for spring framework integration tests is to include application context in test context and autowiring test source in test:
...
public class UpdateServiceTest {
#Autowired
private UpdateService updateService;
...
Mock userService
Option with changing userService to protected and considering that test and source classes are in same package.
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
updateService.userService = mockUserService;
}
Option with reflection with Whitebox:
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Whitebox.setInternalState(updateService, 'userService', mockUserService);
}
The #Value is filled by a property placeholder configurer which is a post processor in the spring context. As your UpdateService is not part of the context it is not processed.
Your setup looks a little like a unclear mixture of unit and integration test. For a unit tests you will not need a spring context at all . Simply make the #Value annotated members package protected and set them or use ReflectionTestUtils.setField() (both shown):
public class UpdateServiceTest {
#InjectMocks
private UpdateService updateService;
#Mock
private UserService mockUserService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(updateService, "successTable", "my_success");
updateService.failedTable = "my_failures";
}
}
For an integration test all wiring should be done by spring.
For this I added a inner config class providing the mock user service (the #Primary is only for the case you have any other user service in your context) and the mock is stored in a static member here to have simple access to the mock from the tests afterwards.
#RunWith(SpringJUnite4ClassRunner.class)
#SpringApplicationConfiguration(classes = {TestApplication.class, UpdateServiceTest.TestAddOn.class})
#WebAppConfiguration
public class UpdateServiceTest {
#Autowired
private UpdateService updateService;
private static UserService mockUserService;
static class TestAddOn {
#Bean
#Primary
UserService updateService() {
mockUserService = Mockito.mock(UserService.class);
return mockUserService;
}
}
}