I'm using spring boot rest API and want to test my service layer. In my service I have autowired few beans and its not through constructor. (I like it that way to keep it short).
In the junit, I have created mocks and for private field which I do want to execute, I have assigned using ReflectionTestUtils.setField. When I debug, the method inside the field is not getting executed which assigned by bean.
Service Class
#Component
public class MyService {
#Autowired
private MyRepository myRepository;
#Autowired
private ResponseMapper responseMapper;
public List<MyObj> getList(String param) throws MyException {
log.info("Getting details");
Optional<List<MyObj>> list = myRepository.findByParam(param);
List<MyObj> data = responseMapper.mapToResponseData(list);
return data;
}
}
Test Class
#RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {
#InjectMocks
private MyService myService;
#Mock
private MyRepository myRepository;
#Mock
private ResponseMapper responseMapper;
#Before
public void setUp() {
ReflectionTestUtils.setField(myService, "responseMapper", responseMapper);
}
#Test
public void getListTest() throws Exception {
when(myRepository.findByParam(anyString()))
.thenReturn(Optional.of(getSampleList()));
List<MyObj> list = myService.getList("param");
assertTrue(list.size() >0);
}
}
This results in Assertion failure and when I debug, the method mapToResponseData in ResponseMapper is not getting executed.
I know I can mock mapToResponseData method also. But I wanted it to execute so I don't have to write another test class for mapper alone.
What am I doing wrong? Is this the right wat to inject bean? And is using constructor in service to inject beans only option?
Related
I am creating a Junit Unit Test to check the createAccount method in service that calls Service a helper method. Please find it below.
Service class
public class AccountServiceImpl {
#Autowired
AccountHelper accountHelper;
#Override
public Account createAccount(Account account) throws CustomerNotFoundException {
accountHelper.checkAccountTypeForCustomer(account);
return accountRepository.save(account);
}
}
Helper Class:
public void checkAccountTypeForCustomer(Account acc) throws CustomerNotFoundException {
Boolean customerExists = customerRepository.existsById(acc.getCustomerId());
if(!customerExists) {
throw new CustomerNotFoundException("604", Message.CUSTOMER_NOT_FOUND);
}
}
AccountServiceTest class
#ExtendWith(MockitoExtension.class)
public class AccountServiceTest {
#Mock
private AccountRepository accountRepository;
#Mock
private CustomerRepository customerRepository;
#Mock
private AccountHelper accountHelper;
#InjectMocks
private AccountService testService;
#Test
void testCreateAccount() throws CustomerNotFoundException {
Account account = Account.builder().
accountType(AccountType.SAVINGS).
openingBalance(BigDecimal.valueOf(3000)).
ifsc("IFSC1").
customerId(1).
build();
testService.createAccount(account);
}
}
Above Test is passing although the customer is not present in the database.
The test is incomplete. But still the statement: testService.createAccount(account);
must fail as per my understanding.
Kindly correct me if I am wrong. I am relatively new to Junit.
However if I place the implementation for checkAccountTypeForCustomer() inside the service method instead of in the helper, the test case fails as expected.
The reason is that accountHelper is mocked in your test, which means that invocation of accountHelper.checkAccountTypeForCustomer(account) doesn't execute your business code.
I recommend you to use Spring mocking in this case, and to specify how your repository is expected to behave. It would look something like this:
#ExtendWith(SpringExtension.class)
class AccountServiceTest {
#MockBean
private CustomerRepository repository;
#Autowired
private AccountService testService;
#Test
void testCreateAccount() throws CustomerNotFoundException {
Mockito.when(repository.existsById(anyInt())).thenReturn(false);
...
CustomerNotFoundException thrown = Assertions.assertThrows(CustomerNotFoundException.class, () -> testService.createAccount(account));
Assertions.assertEquals("the exception message", thrown.getMessage());
}
}
My goal is to use an in-memory database for these unit tests, and those dependancies are listed as:
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
So that the repository instance actually interacts with a DB, and I dont just mock return values.
The problem is that when I run my unit test, the repository instance inside the service instance is null.
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
This is the console output when running my unit test:
null
java.lang.NullPointerException
at com.my.MyService.findAll(MyService.java:20)
at com.my.MyTest.testMy(MyTest.java:23)
My unit test class:
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
My service class:
#Service
public class MyService {
#Autowired
MyRepository myRepository;
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
My repository class:
#Repository
public interface MyRepository extends CrudRepository<MyEntity, Long> {
}
My entity class:
#Entity
public class MyEntity {
#Id
#GeneratedValue
public Long id;
}
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
Basically yes :)
You need to initialise a Spring Context by Annotating your Testclass with #SpringBootTest
The other Problem you have is that you create your MyService Object manually.
By doing so SpringBoot has no chance to inject any Bean for you. You can fix this by simply injecting your MyService in your Testclass. Your Code should look something like this:
#SpringBootTest
public class MyTest {
#Autowired
private MyService myService;
#Test
void testMy() {
int size = myService.findAll().size();
assertEquals(0, size);
}
}
To use #MockBean annotation, you have to use SpringRunner to run the test. Use #RunWith Annotation on top of your test class and pass SpringRunner.class.
#RunWith(SpringRunner.class)
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
The problem here is your service implementation. Using #Autowired to inject the dependency will work when you run the whole app, but it do not allow you to inject a custom dependency when you'll need it, and a good example of this is testing.
Change your service implementation to:
#Service
public class MyService {
private MyRepository myRepository;
public MyService(MyRepository myRepository){
this.myRepository = myRepository;
}
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
This constructor will be called by spring. Then change your test to:
public class MyTest {
#Mock
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService(myRepository);
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
Note I have replaced #MockBean to #Mock as the previous annotation is for injecting a mock bean into the spring context, which is not needed if you're doing unit testing. If you want to boot spring context (which I would not recommend you) you need to configure your test class with #SpringBootTest or some of the other available alternatives. That will convert your test into an integration test.
PD: This test will not work if you don't provide a mock to myRepository.findAll(). Mockito default behaviour is to return null, but you're expecting it to return 0, so you'll need to do something like given(myRepository.findAll()).willReturn(0).
I believe you wish to write an integration test. Here you could remove the MockBean annotation and simply autowire your repository. Also, run with The SpringRunner class.
#RunWith(SpringRunner.class)
public class MyTest {
#Autowired
MyRepository myRepository;
#Autowired
MyService myService
#Test
void testMy() {
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
This should work
I have a Service A which autowired Service B and is using a method from Service B.
Service B autowired another Service C and is using a method from it.
I am writing a test for Service A and the test fails at the call where Service B is invoked.At this point Service C is null.
I have tried #Mock for Service B. Nothing seems to work. How can i successfully test this service which is failing on a service that it isnt explicitly autowiring.
//Service A
#Service
public class FileServiceImpl{
#Autowired
private FileNameServiceImpl fileNameService;
public void createFile(String fileName){
String targetFileName = fileNameService.getTargetFileName(fileName);
}
}
//Service B
#Service
public class FileNameServiceImpl{
#Autowired
private CustomDateService customDateService
public String getTargetFileName(String fileName){
return fileName + customDateService.getCustomDate();
}
}
//CustomDate - this is an interace. The Impl is in another package.
public interfaceCustomDateService{
public String getCustomDate();
}
I am trying to test FileServiceImpl , however it fails with a NullPointer Exception because customDateService is null.
Even though, FileServiceImpl is not calling customDateService.
This is what I have for test thus far:
#Category(UnitTest.class)
#RunWith(MockitoJUnitRunner.class)
public class FileServiceImplTest {
#Spy
#InjectMocks
private FileServiceImpl fileServiceImpl;
#Mock
private FileNameServiceImpl fileNameService;
#Before
public void init() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void testFileName() {
String fileName = "test1.txt";
fileServiceImpl.createFile(fileName); // Test Fails here
Mockito.validateMockitoUsage();
}
As Shane eluded, this sounds like you're maybe integration testing.
If so, make sure the context of your test encompasses the autowired components.
You should post some code, as it's hard to know what exactly is going on here.
If you aren't integration testing, don't rely on autowiring, just construct new ServiceA manually passing in a mocked ServiceB.
Also make sure to initialise your mocks.
private ServiceA serviceA;
#Mock
private ServiceB serviceB;
#BeforeEach
public void setUp() {
MockitoAnnotations.initMocks();
serviceA = new ServiceA(serviceB);
}
EDIT:
First of all, as good practice you should favor constructor injection over field injection in Spring.
So set up the service classes with Autowired constructors.
Also, I'm pretty sure with a mockito Spy you have to initialise the class.
If you switch to use constructor Autowiring you can inject the mocks manually.
#Category(UnitTest.class)
#RunWith(MockitoJUnitRunner.class)
public class FileServiceImplTest {
#Spy
private FileServiceImpl fileServiceImpl;
#Mock
private FileNameServiceImpl fileNameService;
#Before
public void init() throws Exception {
MockitoAnnotations.initMocks(this);
fileServiceImpl = new FileServiceImpl(fileNameService);
}
#Test
public void testFileName() {
String fileName = "test1.txt";
fileServiceImpl.createFile(fileName); <-- now this shouldn't fail
Mockito.validateMockitoUsage();
}
I got 2 modules User and Email, both of them have 1 entry point which is a facade, rest is package scoped. The configuration is done in 2 classes
#Configuration
class UserConfiguration {
#Bean
UserFacade userFacade(UserRepository repository, EmailFacade emailFacade) {
return new UserFacade(repository, emailFacade);
}
}
#Configuration
class EmailConfiguration {
#Bean
EmailFacade emailFacade(EmailSender emailSender) {
return new EmailFacade(emailSender);
}
}
Now, I want to write tests that don't require Spring to start. I implemented a simple InMemoryRepository to make this happen
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade = new EmailFacade(new FakeEmailSender());
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I need some fake objects to instantiate EmailFacade so I wrote fake implementation
public class FakeEmailSender implements EmailSender {
#Override
public void sendEmail(EmailMessage emailMessage) throws RuntimeException {
}
}
In that scenario, I'm testing User domain, so I want to mock Email anyways.
I wrote a test to check if it works
#Test
public void shouldReturnSendingFailed() {
Mockito.when(emailFacade.sendUserVerificationEmail(Mockito.any())).thenReturn(Either.left(EmailError.SENDING_FAILED));
assertThat(userFacade.registerNewUser(RegisterUserDto.builder()
.username(USERNAME_4)
.email(VALID_EMAIL)
.password(VALID_PASSWORD).build()).getLeft(), is(EmailError.SENDING_FAILED));
}
But it isn't... after running this test I got
java.util.NoSuchElementException: getLeft() on Right
edit#
regiserNewUser() method
Either<DomainError, SuccessMessage> register(RegisterUserDto registerUserDto) {
if(userRepository.findUser(registerUserDto.getUsername()).isPresent())
return Either.left(UserError.USERNAME_ALREADY_EXISTS);
var userCreationResult = User.createUser(registerUserDto);
var savedUser = userCreationResult.map(this::saveUser);
var emailDto = savedUser.map(this::createVerificationEmail);
return emailDto.isRight() ? emailFacade.sendUserVerificationEmail(emailDto.get())
: Either.left(emailDto.getLeft());
}
Edit2#
With following test configuration
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I got nullpointer here, last line of registerNewUser().
Try running this code
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
private UserFacade userFacade;
#Before
public void setUp() {
userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
}
}
There are a few issues with your code:
You initialize your mocks twice. You don’t need to call initMocks in the setUp method if you are using Mockito runner
You are trying to inject mocks to already initialized object. But the field you are trying to inject is also passed to the constructor. Please read #InjectMocks doc, to check the strategies used to inject the mocks:
constructor (not used here, already initialized object)
setter (do you have one?)
field (is it not final)
There are details to each strategy (see my questions above). If no staregy is matched, Mockito will fail silently. The fact that you are passing an object in constructor, and rely on setter or field injection afterwards makes this code unnecesarily complex.
I have a Spring-boot project, in witch I have controller, service and mapper layer. Now I want to test a service and I want to mock the mapper. I do it in this way:
Test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(Application.class)
#Transactional
public class SomeServiceTest extends AbstractTransactionalJUnit4SpringContextTests {
#Mock
private AMapper aMapper;
#Autowired
#InjectMocks
AService aService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
executeSqlScript("classpath:insertSomeData.sql", false);
}
#Test
public void testMethod() throws Exception {
//prepareSomeData
aService.callMethod(someData);
verify(aMapper).callTheRightMethod(rightObject);
}
And the service:
#Service
#Transactional(readOnly = true)
public class AServiceImpl implements AService {
#Autowired
BMapper bMapper;
#Autowired
CMapper cMapper;
#Override
#Transactional(readOnly = false)
public SomeReturnObject callMethod(SomeData someData)throws Exception {
//some execution to obtain aResult
if(true){
aMapper.callTheRightMethod(aResult);}
else
aMapper.callWrongMethod(aResult);
}
Now when I execute the test the result is:
Wanted but not invoked:
aMapper.callTheRightMethod{..}
Actually, there were zero interactions with this mock.
When i debug then I see that the method is called, but probably it's the wrong mapper (not the mocked). Have you some tips to figure out that issue?
I can't see the mock interaction recording here. It should come before the actual invocation. It should be something like this.
Mockito.when(aMapper.callTheRightMethod(Mockito.any()).thenReturn(rightObject);
The flow should be like this. Firstly record the mocks, then perform actual invocation and finally verify the mock interactions. As above #Autowire is not needed for the test class. Please remove that too. Instead create a new instance of service class by passing some data through it's constructor. Hope this helps. Happy coding !
I don't exactly understand why would you start up spring context for testing just a service layer. Test only one layer at a time.
That's how I would address the problem. (If something does not compile, my apologies..writing from top of my head)
#RunWith(MockitoJUnit4ClassRunner.class)
public class SomeServiceTest {
#Mock
private AMapper aMapper;
#InjectMocks
AService aService = new AService();
#Test
public void testMethod() throws Exception {
// given
Mockito.doReturn(aResult).when(aMapper).getAResult();
// when
aService.callMethod(someData);
// then
verify(aMapper).callTheRightMethod(rightObject);
}