Hello guys, if you are writing unit test your Java application then you would
have definitely come across JUnit and Mockito, two of the essential unit
testing framework for Java developers. While
JUnit
offers support to run your test, setup and teardown and also assertion to
check the expected and actual output.
Mockito
complements JUnit by offering you a mock based testing approach. Mock is
nothing but a test double which can return a configured value when a certain
method is called. It also provides ability to check if a particular method is
called or not. By using
Mock object
or test double you can check if your code is behaving properly or not in
different scenarios. This is also the key difference between JUnit and
Mockito, JUnit provides infrastructure to run your test while Mockito provide
infrastructure to created Mock objects.
If you are wondering why do we need Mock object? can't we test with actual
object then let me tell you that its not always possible to test with actual
object without involving external dependencies like Database, or an external
System. At that time, using Mock allows you to write unit test and unblock
your development.
For example, recently I was developing a
REST API client
which calls an external service to retrieve discount coupon for products. If I
have to write test for this client then I need the URL of external service to
connect to their test environment which may or may not be ready. In order to
overcome that problem, I can simply mock the HttpClient class which is used to
connect to the third party service.
I can configure my Java 11 HttpClient
to a dummy URL and then I can return the different kind of response to trigger
different workflow in my application. For example, by using mock instance of
HttpClient, I can return a
successful response with code 200, an error response using 400 or even
internal server error with 500 response code.
This gives me ability to test how my application behave in different
scenarios. That's the power of Mockito, even with the actual object its not
easy to mimic all these scenario and requires a lot of setup and effort for
complete testing. That's why Java developer love Mockito, it help them to
write better tests.
Btw, if you are serious about Unit testing and just looking for best online
courses to learn unit testing in Java then you can also checkout this list of
best JUnit and Mockito courses
and if you like books, you can also read these
best JUnit and Mockito books to learn both JUnit and Mockito in depth.
How to use Mockito in Java? Example Tuotrial
Now that you know what is Mockito and what does Mockito offer as well as when
to use Mockito its good time to write code and see how easy or difficult it is
to write Java unit tests using Mockito framework and library.
In this example, we have a
GreetingService which return
greeting message in different language at different time when you call it. For
example, when you first call, it can return Namaste which is Hello in Hindi,
similarly it can return greetings in Japanese also and we will use
Mockito
to configure this behavior.
HelloWorld.java /** * Mockito is a leading mock framework for testing Java classes and interfaces. * In this Java program, we will see how we can use Mockito to test our Java * class, Helloworld, which greet with different message at different time. */ public class HelloWorld { private final GreetingService greetingService; public HelloWorld(GreetingService service) { this.greetingService = service; } public String greet() { return greetingService.getMessage(); } } interface GreetingService { public String getMessage(); }
HelloWorldTestWithMockito.java import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import org.junit.Test; import org.mockito.Mockito; public class HelloWorldTestWithMockito { private final HelloWorld helloWorldTest; private final GreetingService mockGreetingService; private final String greetInHindi = "Namaste !!"; private final String greetInEnglish = "Hello !!"; private final String greetInJapanese = "Moshi Moshi !!"; /* * Creating and Setting up mock objects */ public HelloWorldTestWithMockito() { this.mockGreetingService = mock(GreetingService.class); Mockito.when(mockGreetingService.getMessage()) .thenReturn(greetInHindi) .thenReturn(greetInEnglish) .thenReturn(greetInJapanese); this.helloWorldTest = new HelloWorld(mockGreetingService); } /* * Real testing starts now */ @Test public void greet() { final String firstMessage = helloWorldTest.greet(); final String secondMessage = helloWorldTest.greet(); final String thirdMessage = helloWorldTest.greet(); assertEquals(greetInHindi, firstMessage); assertEquals(greetInEnglish, secondMessage); assertEquals(greetInJapanese, thirdMessage); verify(mockGreetingService, times(2)).getMessage(); } }
If you look closely, we have used a couple of Mockito methods in this program.
First we have used
Mockito.mock() to create a mock
object of GreetingService class. In
most program you will see just
mock() because your
IDE
will use
static import
to import all static method from Mockito library. This will also make you to
write less code and your code will also be more readable.
Once we got the Mock object you can configure it using
when() and
then() method. the when() method takes an argument which is an event like when
getMessage() of GreetingService is called then you can return pre-configured
object like greetInHindi in our case. In short, by using when() and then()
method in Mockito you can sophistically configure your Mock object to mimic
different scenarios under testing.
After that we have also used
verify() method which is
used for verification. By using verify() method you can double check which
method of your class is called and how many times. For example, in our case we
verified that getMessage() of GreetingService is called twice. If its not
called twice then this test will fail and you will come to know about it,
quite convenient? No?
Important points about Mockito Testing
In order to create Mock object, there are a couple of requirement which your
cla ss need to follow. For example, your
testing class must be public, which is not a requirement with JUnit but
Mockito will not allow you to run if your test class is not public as shown in
below error.
java.lang.Exception: Class HellworldTestWithMockito should be public at org.junit.runners.model.FrameworkMethod.validatePublicVoid(FrameworkMethod.java:91) at org.junit.runners.model.FrameworkMethod.validatePublicVoidNoArg(FrameworkMethod.java:70) at org.junit.runners.ParentRunner.validatePublicVoidNoArgMethods(ParentRunner.java:133) at org.junit.runners.BlockJUnit4ClassRunner .validateTestMethods(BlockJUnit4ClassRunner.java:186) at org.junit.runners.BlockJUnit4ClassRunner .validateInstanceMethods(BlockJUnit4ClassRunner.java:166) at org.junit.runners.BlockJUnit4ClassRunner .collectInitializationErrors(BlockJUnit4ClassRunner.java:104) at org.junit.runners.ParentRunner.validate(ParentRunner.java:355)
Now this is to verify that whether Mockito actually record number of invocation to the method. I have put timed(2) to tell Mockito that this method should be called twice, since in code we are calling it three times. Mockito reports it as error :
org.mockito.exceptions.verification.TooManyActualInvocations: greetingService.getMessage(); Wanted 2 times: -> at HelloWorldTestWithMockito.greet(HelloWorldTestWithMockito.java:48) But was 3 times. Undesired invocation: -> at HelloWorld.greet(HelloWorld.java:23) at HelloWorldTestWithMockito.greet(HelloWorldTestWithMockito.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
Let's do one more test, in second call let's compare with first message. This should be reported by JUnit
org.junit.ComparisonFailure: expected:<[Hello] !!> but was:<[Namaste] !!> at org.junit.Assert.assertEquals(Assert.java:115) at org.junit.Assert.assertEquals(Assert.java:144) at HelloWorldTestWithMockito.greet(HelloWorldTestWithMockito.java:45)
Similarly, your class also need a no-argument and default constructor and
should not be final if you want to mock that class for testing. So make sure that if you want to mock your class then
- It must be public
- It must not be final
- and It must have a no-argument constructor
And, here is a simple diagram which explains the difference between an actual object and mock object in Java:
That's all about how to use Mockito in Java with JUnit. There is no
doubt that Mockito is a great library and will help you to write better unit
test. In many cases, it wasn't even possible to write JUnit test case without
Mockito, hence a good knowledge of Mockito goes a long way for Java
programmers. It will not only help you to write better and more sophisticated
unit tests but also to become a better Java developer who is pro at unit
testing.
No comments:
Post a Comment