The other day, I worked on upgrading the version of JMockit used in the unit test (1.13 → 1.46). Make a note of the changes in the old and new JMockit and the correction method at the time of version upgrade.
We hope that it will be useful to the following two types of people.
--I used to use the old JMockit, but those who use the latest version (that is, me and my colleagues) --People who will update JMockit from now on
JMockit is a mock library for Java. You can replace (mock) the behavior of existing methods in your unit tests.
JMockit official page → The JMockit testing toolkit
Replacements are defined using the instance initializer and the special instance variables result
and times
.
new Expectations() {{
obj.method(arg1, arg2, arg3);
result = value;
times = 2;
}};
It also supports fairly powerful operations and can be replaced as follows (see the official page for details).
--Replacement of static method
--Replace only specific methods of objects (Partial Mocking)
--Replacement of objects returned by new
A minor version will be released every few months. You can check past releases from this page:
As you can see from the history, JMockit is a policy that ** truncates old features more and more **. For example, the method Deencapsulation.setField
has been deprecated in 2 months and 2 versions.
Version 1.45 (Jan 27, 2019): Removed the Deencapsulation.setField methods, which were deprecated in version 1.44.
Version 1.44 (Nov 25, 2018): Deprecated the Deencapsulation.setField methods, in favor of @Tested and @Injectable.
Therefore, ** If you try to upgrade the version at once, a lot of changes will be required and the work will be difficult **.
-** Raise the version of JMockit (always follow the latest version) ** -** Migrate to a library other than JMockit **
We recommend either.
JMockit's rivals include Mockito. Mockito has more users and a more natural notation (especially if you're used to Ruby's RSpec).
Also, Mockito alone can only replace normal methods, but when combined with Powermock, it will be possible to make the same powerful "dirty" changes as JMockit. I will.
//Image of mockito notation
when(obj.method(arg1, arg2, arg3)).thenReturn(value);
Therefore, it is recommended to move from ** J Mockit to Mockito ** if possible.
For this product, I gave up migrating to Mockito because there were a lot of tests using JMockit.
You can upgrade the version by the following operations.
However, as explained in "Writing to avoid in the latest version", there are cases where the compilation passes but it does not work as intended. If the test fails, suspect it.
In addition, it is not recommended to jump from the old version to the latest version because there will be too many changes and it will break down.
Also,
--Features that were discontinued in version 1.x are back in 1.y, so you should skip 1.x and upgrade to 1.y --Version 1. (x + 1) has a function for backward compatibility, so you should skip 1.x and upgrade to 1. (x + 1).
I don't think there is a shortcut path like that. ** We recommend that you steadily upgrade one version at a time **.
Read the ** Latest Tutorial ** before upgrading or writing tests.
JMockit is a library with a strong habit in the first place. It hurts when I think, "It's the same as Mockito, only the writing style is different." For example, if you get a mock object for a class that is * @ Mocked
, all instances of that class will be mocked *.
Also, as mentioned above, the functions have been boldly revised and abolished, and the writing style has changed considerably. Experienced JMockit should also read the code before touching it.
Have you read the latest version of the tutorial?
OK!
I will explain the changes of the past version that are likely to have a big impact (I'm sorry for the detailed functions, but please check the release notes etc.).
Previously, JMockit was loaded by annotating the JUnit test class with @ RunWith (JMockit.class)
.
Now the method has changed, and when running a unit test, if you specify the JMockit jar file as the javaagent as a VM option, JMockit will be loaded.
-javaagent:/path/to/jmockit-1.46.jar
For IntelliJ, set in the following location.
"Module name next to the execute button on the toolbar" → Edit Configurations → Templates → JUnit → Configurations → VM options
@ RunWith (JMockit.class)
annotationAs mentioned above, it has been deprecated, so simply delete it.
returns
For the one-argument returns
, useresult =
instead. You can continue to use returns
with two or more arguments.
It is difficult to rewrite returns
one by one, but I wrote about it in another article, so please refer to it (→ How did I safely replace returns (Object) in JMockit?" ).
// Old
new Expectations() {{
obj.method(arg1, arg2, arg3); returns(value);
}};
// New
new Expectations() {{
obj.method(arg1, arg2, arg3); result = value;
}};
NonStrictExpectations
NonStrictExpectations
was the" version of ʻExpectations` that does not cause an error even if the replaced method is not called".
Replace it with ʻExpectationsas shown below, and set the minimum number of calls to 0 with
minTimes = 0`.
new Expectations() {{
mock.get(0); result = "1"; minTimes = 0;
}};
In addition, "I replaced the method, but it is not called" is
--Actually, there was no need to replace that method --Not working as intended
In many cases, please review the test case.
StrictExpectations
Simply rewrite it as Expectations.
Deencapsulation
Deencapsulation was the ability to browse and modify private fields.
package-private
or public
Relax the access restrictions for the fields that you changed with Deencapsulation
, and let the test access with .
. Annotate the field with Guava's @VisibleForTesting
.
You'll be making changes to the body code, but changing access modifiers shouldn't usually lead to bugs [^ 1].
[^ 1]: Of course, it would be a problem if the field that is made public for testing (I want to make it private) is accessed by the production code, but if you use @VisibleForTesting
, you can distinguish whether it is a field that you can access. You should be able to do it.
// Old
//Body code
class Spam {
// ...
private Foo field;
// ...
}
//Test code
x = Deencapsulation.getField(obj, "field");
// New
// Old
//Body code
class Spam {
// ...
@VisibleForTesting
Foo field;
// ...
}
//Test code
x = obj.field;
@Tested
and @Injectable
JMockit has a DI mechanism for testing.
If you are using Deencapsulation.setField
to mock the object under test, it will be replaced by DI.
4 Instantiation and injection of tested classes
.newMockInstance()
Make sure to get the mock instance with @Mocked. Simple rewriting is not possible.
System
are mockedIn older versions JMockit was replaced with any method,
In the current version, native methods like System.currentTimeMillis
are not replaced.
Redesign your test to eliminate the need for the replacement itself, or replace non-native methods.
The part of the upgraded product that uses System.currentTimeMillis
has been changed to replace new Date
instead.
Although not explicitly stated in the documentation, there is a pattern that ** worked fine in the old version and compiles but doesn't work in the new version **.
If you write another method call as a method argument or return value in ʻExpectations, it may not be replaced well. Arguments and return values should be assigned to temporary variables outside of ʻExpectations
. It is better to separate them from the viewpoint of readability.
// NG
new Expectations() {{
foo.method(getX(), getY()); result = getHogeList();
}};
// OK
X x = getX();
Y y = getY();
HogeList hogeList = getHogeList();
new Expectations() {{
foo.method(x, y); result = hogeList;
}};
If you want to replace only a specific method of a class or only a method of a specific instance, specify the class or instance in ʻExpectations`.
At this time, if you mix ʻExpectations arguments with unrelated method calls, you may not be able to mock properly. Separate irrelevant method calls into another ʻExpectations
. It is better to separate them from the viewpoint of readability.
// NG
new Expectations(foo) {{
foo.method(x, y); result = v;
other.othersMethod(); result = value; //This method does not change
}};
// OK
new Expectations(foo) {{
foo.method(x, y); result = v;
}};
new Expectations() {{ //Divide Expectations
other.othersMethod(); result = value;
}};
Recommended Posts