Issue
I am not sure if I am testing a void
returning method the correct way and also if my class-under-test
(cut) requires any change in order to make it 100% testable and bug-proof.
I am seeing NullPointerException
while executing the test
because loginOperations
is not getting set.
Error:
java.lang.NullPointerException
at com.demo.service.LoginService.doLogin(LoginService.java:40)
at com.demo.service.LoginServiceTest.doLogin(LoginServiceTest.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
LoginService.java
@Service
public class LoginService {
@Autowired
private ILoginOperations loginOperations;
public void doLogin(HttpServletRequest request, String result) {
LoginDTO loginDTO = new LoginDTO(request.getParameter("username"), result);
loginOperations.doLogin(loginDTO);
}
}
LoginServiceTest.java
public class LoginServiceTest {
private LoginService instance = new LoginService();
ILoginOperations loginOperations = Mockito.mock(ILoginOperations.class);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
String result = "some string";
@Test
public void doLogin() {
when(request.getParameter("username")).thenReturn("johndoe");
instance.doLogin(request, result); //throws NPE while calling loginOperations.doLogin() because
assertNotNull(instance); //IS THIS THE CORRECT WAY TO TEST A VOID RETURNING METHOD ???
}
}
Now, there are 2 ways to fix the test.
- I can fix the class-under-test by adding a
setter
method forloginOperations
class and call thatsetter
method in thetest
- Change
@Test public void doLogin() {
to@Test(expected = Exception.class) public void doLogin() {
Not sure which one is the best practice above and why.
Another Question:
Other question that I have is how to assert
on a method that returns nothing. There is something like verify()
but not sure how to use it.
Solution
1.You can fix the test case by adding setter method in LoginService
or you can use constructor injection like -
@Autowired
public LoginService(ILoginOperations loginOperations) {
this.loginOperations = loginOperations;
}
- Validating exception as
@Test(expected = Exception.class) public void doLogin()
is certainly not a good idea as doLogin method does not throw exception in normal circumstance.
The better way to test method with void return type is using verification API (example - mockito verification API example). You can also use Mockito's ArgumentCaptor to capture argument and assert state of that argument, along with verification API as -
@RunWith(MockitoJUnitRunner.class)
public class LoginServiceTest {
@Captor
private ArgumentCaptor<LoginDTO> captor;
@Mock
private ILoginOperations loginOperations;
@Mock
private HttpServletRequest mockServletRequest;
@InjectMocks
private LoginService loginService;
@Test
public void validateLogin() {
when(mockServletRequest.getParameter("username")).thenReturn("mock_user_name");
loginService.doLogin(mockServletRequest, "mock_result");
verify(loginOperations).doLogin(captor.capture());
LoginDTO expectedLoginDTO = captor.getValue();
assertThat(expectedLoginDTO.getResult(), is("mock_result"));
assertThat(expectedLoginDTO.getUsername(), is("mock_user_name"));
}
}
There is an excellent article from Martin Fowler about this method of testing - Mocks Aren't Stubs
Answered By - Pankaj
Answer Checked By - Katrina (JavaFixing Volunteer)