JMockit Basic
1 What is JMockit
Jmockit is a Java framework for mocking objects in tests.
2 An Exapmle
//A class you could say hello in native language
public class HelloJMockit {
public String sayHello() {
Locale locale = Locale.getDefault();
if (locale.equals(Locale.CHINA)) {
// Say Chinese in China
return "你好,JMockit!";
} else {
// Say English in other countries
return "Hello,JMockit!";
}
}
}
public class HelloJMockitTest {
@Test
public void testSayHelloAtChina() {
// Suppose we are in China
new Expectations(Locale.class) {
{
Locale.getDefault();
result = Locale.CHINA;
}
};
Assert.assertTrue("你好,JMockit!".equals((new HelloJMockit()).sayHello()));
}
@Test
public void testSayHelloAtUS() {
// Suppose we are in the US
new Expectations(Locale.class) {
{
Locale.getDefault();
result = Locale.US;
}
};
Assert.assertTrue("Hello,JMockit!".equals((new HelloJMockit()).sayHello()));
}
}
Here is a example domostrate the workflow of JMockit. You will learn how to use those annotations, what is Expectation
and so on.
3 Basic
1. Configuration Dependencies
2. Code Structure
A normal test case contains test parameter and test method which consisted with record, replay and verification.
1. Attribute
We can use annotations(@Tested, Mocked, Injectable, Capturing
)on these parameters. We will have a detailed explanation on when to use them.
2. Parameter
The parameter of test method.
Junit doesn't allow parameter in test method except thoese with Jmockit annotations.
The difference between parameters and attributes is the scope. Parameter only applys to the method whereas attribute can be used accross the test class.
3. Record-Replay-Verification
Record: Customized return when receiving some particular inputs.
Replay: replay the test logic/process.
Verification: verification after replay. E.g. times of a method has been called.
public class ProgramConstructureTest {
// This is a test parameter
@Mocked
HelloJMockit helloJMockit;
@Test
public void test1() {
// Record
new Expectations() {
{
helloJMockit.sayHello();
// You are expecting "hello,david" rather than "hello,JMockit"
result = "hello, david";
}
};
// Replay
String msg = helloJMockit.sayHello();
Assert.assertTrue(msg.equals("hello, david"));
// Verification
new Verifications() {
{
helloJMockit.sayHello();
times = 1;
}
};
}
@Test
public void test2(@Mocked HelloJMockit helloJMockit /* This is a test parameter */) {
// Record
new Expectations() {
{
helloJMockit.sayHello();
// You are expecting "hello,david" rather than "hello,JMockit"
result = "hello,david";
}
};
// Replay
String msg = helloJMockit.sayHello();
Assert.assertTrue(msg.equals("hello,david"));
// Verification
new Verifications() {
{
helloJMockit.sayHello();
// verify helloJMockit.sayHello() had been called once
times = 1;
}
};
}
}
3. @Mocked
-
When using the @Mocked annotation on a field, it will create mocked instances(with default value) of each and every new object of that particular class.
-
When our test has some dependent interfaces, we could use @Mocked on them and they will create an instance for them. For example, we are in a distributed system and there is a dependency on remote interface, we could use @Mocked to create an instance.
-
Mocking a class
public class MockedClassTest { // JMockit will instatiate this locale @Mocked Locale locale; // @Mocked on a class @Test public void testMockedClass() { // Static methods will return null Assert.assertTrue(Locale.getDefault() == null); // Non static methods will return null Assert.assertTrue(locale.getCountry() == null); // So as well a new one defined by yourself Locale chinaLocale = new Locale("zh", "CN"); Assert.assertTrue(chinaLocale.getCountry() == null); } }
-
Mocking a interface/abstruct class
public class MockedInterfaceTest { @Mocked HttpSession session; // when @Mocked on interface @Test public void testMockedInterface() { Assert.assertTrue(session.getId() == null); Assert.assertTrue(session.getCreationTime() == 0L); Assert.assertTrue(session.getServletContext() != null); Assert.assertTrue(session.getServletContext().getContextPath() == null); } }
4. @Tested & @Injectable
-
With the @Injectable annotation, only one mocked instance will be created.
-
@Tested means this is the class to be tested.
-
When you need to manage dependencies of class to be tested, you could use these to annotations.
-
different between injetable and mocked
public class MockedAndInjectable { @Test public void testMocked(@Mocked Locale locale) { // Static methods will return null(mocked) Assert.assertTrue(Locale.getDefault() == null); // Non static methods will return null(mocked) Assert.assertTrue(locale.getCountry() == null); // So as well a new one defined by yourself(mocked) Locale chinaLocale = new Locale("zh", "CN"); Assert.assertTrue(chinaLocale.getCountry() == null); } @Test public void testInjectable(@Injectable Locale locale) { // Static method will not be mocked Assert.assertTrue(Locale.getDefault() != null); // mocked but only apply to this 'locale' Assert.assertTrue(locale.getCountry() == null); // If you create new instance, it won't be affected Locale chinaLocale = new Locale("zh", "CN"); Assert.assertTrue(chinaLocale.getCountry().equals("CN")); } }
-
Use of Injectable and Tested
public interface MailService { public boolean sendMail(long userId, String content); }
public interface UserCheckService { public boolean check(long userId); }
public class OrderService { MailService mailService; @Resource UserCheckService userCheckService; public OrderService(MailService mailService) { this.mailService = mailService; } public boolean submitOrder(long buyerId, long itemId) { if (!userCheckService.check(buyerId)) { return false; } // order logic ... if (!this.mailService.sendMail(buyerId, "下单成功")) { return false; } return true; } }
If we want to test submitOrder but it depends on the MailService and UserCheckService and we don't want to connect to the mail server nad user authentication server.
public class TestedAndInjectable { //JMockit will initiate this parameter @Tested OrderService orderService; long testUserId = 123456l; long testItemId = 456789l; @Test public void testSubmitOrder(@Injectable MailService mailService, @Injectable UserCheckService userCheckService) { new Expectations() { { mailService.sendMail(testUserId, anyString); result = true; userCheckService.check(testUserId); result = true; } }; // JMockit instatiated mailService and injected to OrderService via attribute in orderSerice //JMockit instatiated userCheckService and injected to OrderService via attribute in orderSerice Assert.assertTrue(orderService.submitOrder(testUserId, testItemId)); } }
5. @Capturing
The last annotation, @Capturing will behave like @Mocked, but will extend its reach to every subclass extending or implementing the annotated field's type.
6. Expectation
new Expectations() {
// this is an Expectations anonymous inner class
{
//method call
//result = ...
//other method call
//result = ...
// ...
}
};
// we can define another record before replay.
new Expectations() {
{
//...
}
};
-
Use Mocked objects to record
public class ExpectationsTest { @Mocked Calendar cal; @Test public void testRecordOutside() { new Expectations() { { cal.get(Calendar.YEAR); result = 2016; cal.get(Calendar.HOUR_OF_DAY); result = 7; } }; Assert.assertTrue(cal.get(Calendar.YEAR) == 2016); Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7); Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 0); } }
-
Build injection methods
public class ExpectationsConstructorTest2 { @Test public void testRecordConstrutctor1() { Calendar cal = Calendar.getInstance(); // Pass the class to be mock to as parameter, you could only mock part of the actions new Expectations(Calendar.class) { { cal.get(Calendar.HOUR_OF_DAY); result = 7; } }; Calendar now = Calendar.getInstance(); Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == 7); Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate()); } @Test public void testRecordConstrutctor2() { Calendar cal = Calendar.getInstance(); // Pass the class to be mock to as parameter, you could only mock part of the actions and only apply to this object new Expectations(cal) { { cal.get(Calendar.HOUR_OF_DAY); result = 7; } }; Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7); Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate()); // now is another object and will not be mocked Calendar now = Calendar.getInstance(); Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == (new Date()).getHours()); Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate()); } }
7. MockUp & @Mock
public class MockUpTest {
@Test
public void testMockUp() {
// customize the get() in Calender.class
new MockUp<Calendar>(Calendar.class) {
@Mock
public int get(int unit) {
if (unit == Calendar.YEAR) {
return 2017;
}
if (unit == Calendar.MONDAY) {
return 12;
}
if (unit == Calendar.DAY_OF_MONTH) {
return 25;
}
if (unit == Calendar.HOUR_OF_DAY) {
return 7;
}
return 0;
}
};
Calendar cal = Calendar.getInstance(Locale.FRANCE);
Assert.assertTrue(cal.get(Calendar.YEAR) == 2017);
Assert.assertTrue(cal.get(Calendar.MONDAY) == 12);
Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 25);
Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
// Other methods won't be mocked.
Assert.assertTrue((cal.getFirstDayOfWeek() == Calendar.MONDAY));
}
}
-
Limitation
- A class has many instatnces and you need to mock only one of them;
- Mock on class that dynamically generated by AOP;
- More codes.
-
MockUp & @Mock is suitable for mock some common methods and reduce new Exceptations.
8. Verifications
- Verificaltions are used to verify the times or order for paticular methods called.
new Verifications() {
{
//method call
//times/minTimes/maxTimes
// ...
//another method call
//times/minTimes/maxTimes
}
};
// you can specify another verification after replay
new Verifications() {
{
//...
}
};
public class VerificationTest {
@Test
public void testVerification() {
//record
Calendar cal = Calendar.getInstance();
new Expectations(Calendar.class) {
{
cal.get(Calendar.YEAR);
result = 2016;
cal.get(Calendar.HOUR_OF_DAY);
result = 7;
}
};
// replay
Calendar now = Calendar.getInstance();
Assert.assertTrue(now.get(Calendar.YEAR) == 2016);
Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == 7);
// verify
new Verifications() {
{
Calendar.getInstance();
times = 1;
cal.get(anyInt);
times = 2;
}
};
}
}
- In daily we tends to use Assert to verify some results rather than caring about if a method has been called and so on. So in verification stage, we could use Assert to replace new Verifications.