[JAVA] A note for those who live with JMockit

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

What is JMockit

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

Features of JMockit version upgrade

A minor version will be released every few months. You can check past releases from this page:

JMockit - Development history

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.

Mockito is recommended over JMockit!

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.

Version upgrade work flow

You can upgrade the version by the following operations.

  1. Increase the version of JMockit
  2. Rewrite functions that were abolished in the version upgrade
  3. Compile
  4. Modify the code until it compiles
  5. Test execution
  6. Modify the code until the test passes

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 version of the tutorial

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.

JMockit - Tutorial

Main obsolete functions and rewriting methods

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.).

How to run the test

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) annotation

As mentioned above, it has been deprecated, so simply delete it.

1 argument 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 withminTimes = 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.

1. Change the field to 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;

2. Use @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.

Methods such as System are mocked

In 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.

Writing styles that should be avoided in the latest version

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 **.

Write another method call in the argument / return value

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;
}};

Write irrelevant methods in ʻExpectations` with arguments

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

A note for those who live with JMockit
[For those who create portfolios] Search function created with ransack
A must-see for those who don't understand the second Heroku deployment!
[For those who create portfolios] How to use binding.pry with Docker
A review note for the class java.util.Scanner
Java Programmer Gold SE 8 Qualification Summary (for those who are familiar with Java)
A review note for the class java.util.Optional
A review note for the class java.util.Objects
A review note for the package java.time.temporal
Access with Selenium as a countermeasure for navigator.webdriver
A note for when someone who was Java Java until yesterday came to touch Scala
Create a widget template for iOS14 with Intent Configuration.
[Java basics] Let's make a triangle with a for statement
A note for Initializing Fields in the Java tutorial
[Note] Build a Python3 environment with Docker in EC2
[For those who create portfolios] How to use font-awesome-rails
[Note] I suddenly can't build with Docker for windows.
[Note] Create a java environment from scratch with docker
(For myself) Build gitlab with ubuntu18.04 + docker for home (Note)
A rock-paper-scissors game for two players who play against each other in threads with Java
I put in a gem bullet (for those who don't notice the N + 1 problem themselves)
For those who have deleted a document in Firestore but the subcollection does not disappear