Beruflich Dokumente
Kultur Dokumente
2.1. Introduction
First of all, let's talk about what JMockit is: a Java framework for mocking objects in tests
(you can use it for both JUnit and TestNG ones).
It uses Java's instrumentation APIs to modify the classes' bytecode during runtime in order
to dynamically alter their behavior. Some of its strong points are its expressibility and its
out-of-the-box ability to mock static and private methods.
Maybe you're new to JMockit, but it's definitely not due to it being new. JMockit's
development started in June 2006 and its first stable release dates to December 2012, so it's
been around for a time now (current version is 1.24 at the time of writing the article).
Why JMockit?
Well the most important reason to opt for JMockit is because it lets us mock anything. And
we mean it. Literally anything.
1. On the record phase, during test preparation and before the invocations to the
methods we want to be executed, we will define the expected behavior for all tests
to be used during the next stage.
2. The replay phase is the one in which the code under test is executed. The invocations
of mocked methods/constructors previously recorded on the previous stage will now
be replayed.
3. Lastly, on the verify phase, we will assert that the result of the test was the one we
expected (and that mocks behaved and were used according to what was defined in
the record phase).
With a code example, a wireframe for a test would look something like this:
1
2 @Test
3 public void testWireframe() {
// preparation code not specific to JMockit, if any
4
5 new Expectations() {{
6 // define expected behaviour for mocks
7 }};
8
9 // execute code-under-test
10
new Verifications() {{
11
// verify mocks
12 }};
13
14 // assertions
15 }
16
3. Creating Mocks
1. @Mocked will mock everything and all instances of that class, and @Injectable will only
mock a specific method/field of one instance of that class.
2. When you have done explicit injection (like using Guice), you have to use @Injectable,
otherwise you will get:
java.lang.IllegalArgumentException: No constructor in tested class that can be satisfied by
available tested/injectable values.
?
/************************* Person.java ****************/
1
public class Person {
2
3 private String name;
4
5
6 public Person(){
7 name = "Default Abhi";
8 }
9
public String getName() {
10
return name;
11 }
12
13 public void setName(String name) {
14 this.name = name;
}
15 }
16
17 /******************* PersonTestConstructor.java ***********/
18 public class PersonTestConstructor {
19
20 @Test
21 public void testGetName() {
new MockUp<Person>() {
22 @Mock
23 public void $init() {
24 //Dont assign name variable at all
25 //Leave it null
26 }
27
};
28
29 Person p = new Person();
30 String name = p.getName();
31
32 assertNull("Name of person is null",name);
33 }
34
35 }
Here we have a class Person whose default constructor initializes the name variable to "Default Abhi" In the
test class PersonTestConstructor, I mock the constructor by using a special mock method $init to do nothing.
Remember the to put the @Mock annotation above. I have spent hours trying to debug only to find out that I
forgot.
In the following example the static initialization block calls a static method and sets the bankBalance to 100.
But in our test case we want to start off with 500.
Note the $clinit method in the BankTest class that calls the same method with 500 as the argument.
?
1 /***************** Bank.java ******************/
2 public class Bank {
3
static int balanceAmount;
4
5 //Static block begins
6 static {
7 updateBalance(100);
8 }
9
10 public static void updateBalance(float balance) {
balanceAmount += balance;
11 }
12 }
13
14 /*********************** BankTest.java **********/
15 public class BankTest {
16
17 @Test
public void testBankStaticBlock(){
18
19 new MockUp<Bank>(){
20
21 @SuppressWarnings("unused")
22 @Mock
23 public void $clinit(){
24 Bank.updateBalance(500);
}
25 };
26
27 assertEquals("The balance amount is 500", 500, Bank.balanceAmount);
28
29 }
30 }
31
We'll see both of them below. First using the Expectations class
In this technique we will be creating a Expectations anonymous inner class and define the expected behaviour
when a method is invoked with particular arguments.
This time the Bank class uses the DBManager to retrieve the Account Holder Name to process the account.
However we want to mask all the DB related code and return a name, say "Abhi"
when retrieveAccountHolder() is invoked with the Account ID as 10.
Notice the DBManager is declared within the Expectations class and is mocked by default. However if you
wanted to declare it outside the test method then it should be annotated with the @Mocked annotation.
The answer is - "The Test would have failed because of UnexpectedInvocation Exception" Why ? The reason
- Expectations is a strict checking mechanism. Placing the creation of Bank object after the Expectations block
would have called the constructor of DBManager. But in the Expectations block we have strictly mentioned
that :
But then, our test cases may not be so rigid. In that case use the NonStrictExpectations class.
It places no restrictions on the number of times the mocked method is called or if any other method of the
same object is called. Most of the time you will be using NonStrictExpectations class.
One last tip before we move onto the MockUp apis. In the previous section point number 2 states that the
argument passed must be 10.
What to do if we require, when any integer is passed into the retrieveAccountHolderName() as an argument
the return string should be "Abhi"? For this JMockit provides us with a field called anyInt. The
line dbManager.retrieveAccountHolderName(10) should be replaced
with dbManager.retrieveAccountHolderName(anyInt). We will look into more of this in this section.
MockUp apis are categorized as State Based testing because the mocks you write are not dependent on the
behaviour of the test class. They are defined independently and work the same in any case.
?
1 /**************** DBManager.java *************/
2 public class DBManager {
3
4 public String retrieveAccountHolderName(int accountId ){
String accountHolderName = null;
5
6 //connect to db
7 //retrieve the Account Holder Name
8
9 return accountHolderName;
10 }
11 }
12
/**************** Bank.java ****************/
13 public class Bank {
14
15 DBManager dbManager =new DBManager();
16
17
18 public String processAccount(int accountID){
19
20 //Some other code goes here
21
22 String accountHolderName = dbManager.retrieveAccountHolderName(accountID);
23
//some more processing code
24
25 return accountHolderName;
26 }
27 }
28
29 /**************** BankTest.java ****************/
30 public class BankTest {
31
@Test
32 public void testBankProcessAccount() {
33
34 new MockUp<DBManager>() {
35
36 @SuppressWarnings("unused")
37 @Mock
38 public String retrieveAccountHolderName(int accountId ){
return "Abhi";
39 }
40 };
41
42 Bank bank = new Bank();
43
44 String name = bank.processAccount(20);
45
46 assertEquals("Account holder Name for A/C id 20 is 'Abhi' ","Abhi",name);
47
}
48 }
How to mock private methods in JMockit?
Pretty cool, isn't it? To be able to modify private methods?
The follwing two examples will give you how a private method is redefined first by using the Expectations api
and then the MockUp api.
First the Expectation API. Yes of course, we can't call the private method directly in our Test class. BUT JMockit
offers a neat Reflection Utility called the Deencapsulation class.
This class has static methods that can invoke private methods and access private fields.More about it later
?
1 /****************** Simple.java ***********/
2 public class Simple {
3
4
5 private String iAmPrivate(){
return "Private Method";
6
}
7
8 public String publicCallsPrivate(){
9 return iAmPrivate();
10 }
11 }
12
/**************** SimpleTest.java *********/
13
public class SimpleTest {
14
15 @Test
16 public void testPublicInvokesPrivate(){
17
18 //Make simple final to be used in the Expectations inner class
19 final Simple simple = new Simple();
20
//pass simple as argument to make it a Mocked type
21
//in the Expectations class
22 new Expectations(simple){
23 {
24 Deencapsulation.invoke(simple, "iAmPrivate");
25 returns("I got INVOKED");
}
26
};
27
28 String str = simple.publicCallsPrivate();
29 assertEquals("The returned string is - I got INVOKED","I got INVOKED",str);
30 }
31
32 }
Now for the MockUp way
?
1
2
/****************** Simple.java ***********/
3 public class Simple {
4
5
6 private String iAmPrivate(){
7 return "Private Method";
8 }
9
10 public String publicCallsPrivate(){
return iAmPrivate();
11 }
12 }
13
14 /**************** SimpleTest.java *********/
15 public class SimpleTest {
16
17 @Test
public void testPublicInvokesPrivateMockUp(){
18
19 new MockUp<Simple>(){
20
21 //Override the private method
22 //Dont provide any ACCESSS MODIFIER!
23 @Mock
24 String iAmPrivate(){
return "MockUp Invoke";
25 }
26
27 };
28
29 Simple simple = new Simple();
30
31 String str = simple.publicCallsPrivate();
32 assertEquals("String returned - MockUp Invoke","MockUp Invoke",str);
33
}
34 }
The only thing to be done is to mark the field in the test class with @Mocked or create a local variable in the
anonymous Expectations class.
Lets go back to the Bank example. Our Bank class has a method called makeConection() that internally
calls DBManager's getConnectionString() static method. However when we write the test method we want
the getConnectionString() to return altogether a different String.
?
1 /********** DBManager.java **************/
2 public class DBManager {
3
public static String getConnectionString(){
4 return "ORIGINAL";
5 }
6 }
7
8 /********** Bank.java **************/
9 public class Bank {
10
public String makeConnection(){
11 //some connection related code
12 //goes here
13
14 // call to static method
15 String conStr = DBManager.getConnectionString();
16
17 // If the connection String
// is anything other than
18 // ORIGINAL return FAIL
19 if(conStr.equals("ORIGINAL"))
20 return "SUCCESS";
21 else
22 return "FAIL";
}
23 }
24
25 /********** BankTest.java **************/
26 public class BankTest {
27
28 @Test
29 public void testMakeConnection(){
30
new NonStrictExpectations(){
31 // DBManager is mocked here
32 DBManager dbManager;
33
34 {
35 DBManager.getConnectionString();
36 returns("DUPLICATE");
}
37 };
38
39 Bank bank = new Bank();
40 String status = bank.makeConnection();
41
42 assertEquals("Status is FAIL","FAIL",status);
43 }
}
44
45
There may be a private method you want to test. Generally if you read any article-they'll usually advise to
move it to more broader visibility. I personally wont agree to this.
If you are stuck with writing test methods for legacy code, you cant just refactor the code and check it in. The
latter is a mammoth process in itself.
Another option is to use the native reflection api's provided by java. I've tried this. It becomes a bowl of hot
vegetable noodles you wont be able to handle later on.
Back to Deencapsulation class! The most used methods are invoke() , setField() and the getField().
The names are pretty intuitive and you'll be using them all over your junits when dealing with private variables
and methods.