Junit Test Pass even when the method called inside may fail - java

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());
}
}

Related

Junit Mocked bean is not executing

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?

First mock of the method applied always

I want to test a few cases in a method by mocking external dependency to return different results for every test case. But when always returns what is defined at first time (in this example - empty set) and that brokes the next tests.
If I run tests one by one they pass successfully but when I run the whole class only the first test pass and others fail.
Testing class:
class ExampleTest {
#Mock
private Dao dao;
#Mock
private Validator validator;
#Spy
#InjectMocks
Controller controller;
#BeforeEach
void setUp() {
initMocks(this);
}
private final static Set DATA = Set.of("data1", "data2");
#Test
void firstTest() throws UserDashboardException, DashboardException, WidgetException {
when(validator.filter(DATA)).thenReturn(Collections.emptySet());
assertThrows(Exception.class, () -> controller.create(DATA));
}
#Test
void secondTest() throws UserDashboardException, DashboardException, WidgetException {
when(validator.filter(DATA)).thenReturn(DATA);
controller.create(DATA);
verify(dao, times(1)).create(eq(DATA));
}
}
Tested class:
public class Controller {
private Dao dao;
private Validator validator;
public Controller(Dao dao,Validator validator) {
this.dao = dao;
this.validator = validator;
}
public String create(Set<String> data) {
data = validator.filter(data);
if (data.isEmpty()) {
throw new Exception("Invalid data.");
}
return dao.create(data);
}
}
So, in both tests create method throws an exception which is not what I expect. Maybe I miss some point?
Have you tried with doReturn method?
doReturn(DATA).when(validator).filter(DATA)
which can be import from org.mockito.Mockito.doReturn;
Edited: there might be a bug inside your code implementation:
data = validator.filter(data);

Mockito when isn't replacing original method behaviour

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.

Spring + Mockito injection not working

I'm new with Spring and Mockito testing. I couldn't find answer to my problem on stackoverflow.
I have the following classes:
#EnableScheduling
#Service
public class ServiceEx {
private Queue<Object> tasks = new ConcurrentLinkedQueue();
public void addItem(Object task) {
tasks.add(task);
}
#Scheduled(fixedRate = 30000)
public void executePendingTask() {
tasks.remove();
}
public void drop() {
tasks.clear();
}
public boolean isEmpty() {
return tasks.isEmpty();
}
}
#Controller
#RequestMapping("/drop")
public class ControllerEx {
private ServiceEx service;
#Inject
public ControllerEx(ServiceEx service) {
this.service = service;
}
#RequestMapping(method = RequestMethod.GET)
public String dropTasks(Model model) {
service.drop();
return "redirect:/home";
}
}
And my testing class looks like :
public class ControllerTest {
#Inject
private ServiceEx service;
#InjectMocks
private ControllerEx controller;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void test() {
service.add(task1);
service.add(task2);
this.mockMvc.perform(get("/drop")).andExpect(status().is3xxRedirection());
assertTrue(service.isEmpty());
}
}
My problem is that service is null in both ControlleEx and ControllerTest and i don't want to mock it. I tried several annotations, but didn't find which one to use.
What have i done wrong?
Looking at your code you seem to be mixing unit tests with integration tests.
MockMvc is usually used to do integration tests starting at the controller level. this means that you need a configured and started applicationContext to successfully use that. If that's what this test class is supposed to do then I don't see the use of Mocks, unless you wire them in the application context, your controller won't use them.
If you want to do integration testing, but want to Mock or stub out certain functionality (which in my opinion should only be done because of dependencies on external systems) you should think about wiring some stubs in your applicationContext for this test instead of trying to use Mockito for this.
Also keep in mind that by default, the applicationContext is re-used to run all your tests, which could mean that stubbing for one test could affect an other.

Using #Mock and #InjectMocks

I'm currently studying the Mockito framework and I've created several test cases using Mockito.
But then I read that instead of invoking mock(SomeClass.class) I can use the #Mock and the #InjectMocks - The only thing I need to do is to annotate my test class with #RunWith(MockitoJUnitRunner.class) or use the MockitoAnnotations.initMocks(this); in the #Before method.
But it doesn't work - It seems that the #Mock won't work!
Here is my 2 codes revisions - one using the annotations and one without.
What am I doing wrong?
public class ReportServiceImplTestMockito {
private TaskService mockTaskService; // This is the Mock object
private ReportServiceImpl service;
#Before
public void init(){
service = new ReportServiceImpl();
mockTaskService = mock(TaskServiceImpl.class);
service.setTaskServiceImpl(mockTaskService);
}
/// ...
Some tests
}
As I said - this work great.
But the following wont:
#RunWith(MockitoJUnitRunner.class)
public class ReportServiceImplTestMockito {
#Mock
private TaskService mockTaskService;
#InjectMocks
private ReportServiceImpl service;
// Some tests
}
And here is the ReportServiceImpl class:
#Service
public class ReportServiceImpl implements ReportService {
#Autowired
private TaskService taskServiceImpl;
public ReportServiceImpl(){}
public ReportServiceImpl(TaskService taskService){
this.taskServiceImpl = taskService;
}
public void setTaskServiceImpl(TaskService taskServiceImpl) {
this.taskServiceImpl = taskServiceImpl;
}
}
What am I missing?
O.K, I got my mistake!!!
I've used the #InjectMocks but initialized the same variable in the init() method...
So what happened was that mockito injected the mock objects to my variable - but seconds later I ran it over - initializing that very same variable!!!
Your code works fine for me using Mockito 1.9.
Using an 1.8+ version of Mockito I get a very specific error message telling me exactly how to fix the problem. As php-coder suggests: For Mockito 1.8+ you need to initialize the field.
Did you see this or any other error message?
Edit:
The following code works for me. Small changes:
Removed Spring annotations
Removed Interface
Added Getter
Added empty TaskService
Added test with System.out.println
Does it produce an error for you? :
Service:
public class ReportServiceImpl {
private TaskService taskServiceImpl;
public ReportServiceImpl() {
}
public ReportServiceImpl(TaskService taskService) {
this.taskServiceImpl = taskService;
}
public void setTaskServiceImpl(TaskService taskServiceImpl) {
this.taskServiceImpl = taskServiceImpl;
}
public TaskService getTaskServiceImpl() {
return taskServiceImpl;
}
}
Dependency:
public class TaskService {
}
Test, prints mockTaskService:
#RunWith(MockitoJUnitRunner.class)
public class ReportServiceImplTestMockito {
#Mock
private TaskService mockTaskService;
#InjectMocks
private ReportServiceImpl service;
#Test
public void testMockInjected() {
System.out.println(service.getTaskServiceImpl());
}
}
I'm not sure, but try to create new instance of ReportServiceImpl manually (as you did in working example):
#InjectMocks
private ReportServiceImpl service = new ReportServiceImpl();

Categories

Resources