[Java] JUnit4 test case example

Unfamiliar with @Rule

Are you writing JUnit? You are writing. I also write. I have a lot of projects for government offices, but the environment for government offices, finance, and other hard-working projects is usually old! There is still Java 6 and so on. …… As of 2019, even completely new projects are usually made with Java 1.6 + Spring 1.x, so ... I feel that this is a real project for government agencies and famous companies (other than IT companies): joy: When I first touched it, it was still JUnit3, but now JUnit4 is the mainstream. I haven't seen JUnit 5 in business (projects of solid companies) because I don't bother to change the testing frame in the existing project.

But I'm still not used to JUnit 4 ... That's why I wrote down the patterns I often write about the @Rule annotation, which I write by googled and searching for people's code every time. As an item, it looks like this.

--When an assert error occurs, do not stop and accumulate (Error Collector) --Get test method name (TestName) --Assess the exception that occurred (ExpectedException) --Define the processing before and after the test and when it succeeds or fails (Test Watcher)

When using PowerMock

I mentioned it a little in [Supplement 2](#Supplement 2), but the compatibility between PowerMock and @Rule is not very good. PowerMockRule of PowerMock cannot be used, recognition that class is @RunWith annotation (@RunWith (PowerMockRunner.class)). Then, if you get a not compile error yes execution error (PowerMock related, or the object with @Rule is null or it doesn't work), you have to give up either @Rule or PowerMock. It may be solved depending on the combination of versions, but since the business is fixed version, trial and error is impossible, so I'm sorry I have not reached the correct answer. Also, if you mock the constructor with PowerMock, there is a problem that coverage cannot be obtained with EclEmma. (This can be solved depending on the version combination)

Sample code

Test target

Sample.java


public class Sample {
	public static boolean isEmpty(String value) {
		return (value == null || "".equals(value)) ? true : false;
	}

	public static int parseInt(String value) throws Exception {
		try {
			return Integer.parseInt(value);
		} catch (NumberFormatException e) {
			throw new Exception("Conversion failure", e);
		}
	}
}

Test code

If the mock setting or assertion is complicated, the method is cut for each case, but in the test case of the general-purpose static utility method, multiple cases are executed by one test method in this way. When the argument is a map, the private method with a / separated String as an argument is cut and converted to a map before passing. A group that often cuts private methods even in test classes.

SampleTest.java


import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.text.DecimalFormat;
import java.text.MessageFormat;

import org.hamcrest.core.IsInstanceOf;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.junit.rules.ExpectedException;
import org.junit.rules.TestName;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.hamcrest.Matcher;

public class SampleTest {
	//Accumulation of assert errors
	@Rule
	public ErrorCollector errs = new ErrorCollector();
	//Get test method name
	@Rule
	public TestName name = new TestName();
	//Exception assert content setting
	@Rule
	public ExpectedException thrown = ExpectedException.none();
	//Output of start / end message
	@Rule
	public TestWatcher watcher = new TestWatcher() {
		@Override
		protected void starting(final Description desc) {
			System.out.println(MessageFormat.format("Test start. : {0}", desc));
		}

		@Override
		protected void succeeded(final Description desc) {
			System.out.println(MessageFormat.format("Test succeeded. : {0}", desc));
		}

		@Override
		protected void failed(final Throwable e, final Description desc) {
			System.err.println(MessageFormat.format("Test failed. : {0} \r\n*** Exception : {1}.", desc, e.getMessage()));
		}

		@Override
		protected void finished(final Description desc) {
			System.out.println(MessageFormat.format("Test Finished. : {0}", desc));
		}
	};

	@Test
	public void test_isEmpty() {
		String[][] args = new String[][]{
			new String[]{null, "true"},
			new String[]{"", "true"},
			new String[]{"ABC", "false"},
		};

		int i = 1;
		for (String[] arg : args) {
			//Assert error
  			this.errs.checkThat(this.getAssertMsg(i++), 
  					Sample.isEmpty(arg[0]), is(Boolean.valueOf(arg[1])));
		}
	}

	@Test
	public void test_parseInt_Abnormal system() throws Exception {
		//Assert thrown exception
		this.thrown.expect(Exception.class);
		this.thrown.expectMessage("Conversion failure");
		this.thrown.expectCause(IsInstanceOf.<Throwable>instanceOf(NumberFormatException.class));

		Sample.parseInt("AAA");
	}

	private String getAssertMsg(int idx) {
		//Get test method name
		return this.name.getMethodName() + " : " + new DecimalFormat("000").format(idx);
	}
}

ErrorCollector If the old-fashioned assert ○○ causes an assert error, the test method ends there. In the case of a test method that repeats test execution and assertion like test_isEmpty () in the above source, if an assert error occurs in the first case, the results of the second and subsequent cases will not manage the first assert error. I can't confirm. By using ErrorCollector, the assert error that occurred on the way is stocked and the next processing is performed. You can see the stocked assert errors in the fault trace field by selecting the test method from Eclipse's JUnit view.

TestName Recommended to use in combination with Error Collector. For example, pass a message to the first argument of checkThat to make it easier to understand where the assert error occurred. In the above source, it is used in getAssertMsg (int idx). For an example of getting a test method name that does not use TestName, see [Supplement 1](#Supplement 1).

ExpectedException Exception assertion can be written as @Test (expected = Exception.class), but this only validates the thrown exception type. Messages and cause exceptions cannot be verified ... So I used to write it in try ~ catch, but if you can use @Rule, you can write it more quickly. In the above source, only the type is verified for the cause exception, but you can also verify the message at the same time with the following feeling.

thrown.expectCause(allOf(
	instanceOf(NumberFormatException.class),
	hasProperty("message", is("Message content"))));

However, if ExpectedException.expectCause is an old version, it will not compile and you will struggle, so in such a case it is better to give up and write with try ~ catch. The one that gets stuck in an existing project without updating the library ... If it is JUnit 4.12 or later, I think that the example writing method is OK. It didn't work with 4.10 ...

TestWatcher The one that can be used with Junit 4.11 or later. You can write the process to be called before the test is executed, when the prerequisites are not met, when the test succeeds, when the test fails, and after the test ends. When the prerequisite is not satisfied, it is not written in the above source, but it is skipped (AssumptionViolatedException, Description). I've never used it. In the above source, the log is output to the console, but of course you can use the logger to output not only the console but also as you like. It's not decided that it's for log output in the first place, so I wonder if I can use this instead of @Before or @After.

As for the execution order combined with @BeforeClass, @Before, @After, @AfterClass, there was an article that posted the verification result, so please see below. JUnit4 execution order --umezucolor diary With this execution order, I personally think that the processing specified by TestWatcher is about log output. For example, if you need to delete a file every time you execute a test method, write it with @Before ...


Execution example

In Eclipse, the cases where an assert error occurred are output together in the failure trace of the JUnit view. It is an assumed result that causes an assert error, and the result executed from the console looks like this. By the way, when you run it from the console, it looks like this. I put the JUnit jar in lib. java -cp ./;./lib/*; org.junit.runner.JUnitCore SampleTest

JUnit version 4.12
.Test start. : test_parseInt_Abnormal system(SampleTest)
Test succeeded. : test_parseInt_Abnormal system(SampleTest)
Test Finished. : test_parseInt_Abnormal system(SampleTest)
.Test start. : test_isEmpty(SampleTest)
Test failed. : test_isEmpty(SampleTest)
*** Exception : There were 2 errors:
  java.lang.AssertionError(test_isEmpty : 002
Expected: is <true>
     but: was <false>)
  java.lang.AssertionError(test_isEmpty : 003
Expected: is <false>
     but: was <true>).
Test Finished. : test_isEmpty(SampleTest)
EE
Time: 0.043
There were 2 failures:
1) test_isEmpty(SampleTest)
java.lang.AssertionError: test_isEmpty : 002
Expected: is <true>
     but: was <false>
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.rules.ErrorCollector$1.call(ErrorCollector.java:65)
        at org.junit.rules.ErrorCollector.checkSucceeds(ErrorCollector.java:78)
        at org.junit.rules.ErrorCollector.checkThat(ErrorCollector.java:63)
        at SampleTest.test_isEmpty(SampleTest.java:64)
~ Omitted ~
2) test_isEmpty(SampleTest)
java.lang.AssertionError: test_isEmpty : 003
Expected: is <false>
     but: was <true>
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.rules.ErrorCollector$1.call(ErrorCollector.java:65)
        at org.junit.rules.ErrorCollector.checkSucceeds(ErrorCollector.java:78)
        at org.junit.rules.ErrorCollector.checkThat(ErrorCollector.java:63)
        at SampleTest.test_isEmpty(SampleTest.java:64)
~ Omitted ~

FAILURES!!!
Tests run: 2,  Failures: 2

Supplement 1

The test method name can also be obtained from the stack trace. It's late. It's a method that has been known for a long time. Since getStackTrace [2] is the depth of the stack trace, it may need to be adjusted if the hierarchy for calling test methods in JUnit is not constant.

private String createAssertMsg(int idx) {
	return Thread.currentThread().getStackTrace[2].getMethodName() + " : " + new DecimalFormat("000").format(idx);
}

Supplement 2

In addition, around @Rule, if you want to use PowerMock and write @RunWith (PowerMockRunner.class), you may not be able to use it. It's not completely useless, TestName and ErrorCollector may or may not work, but why ... By the way, I was able to use the above two when I wanted to combine Spring DI and the class annotation was like this. Well, the ExpectedException of exception validation didn't work in this case either, so I wrote it with try ~ catch ...

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@PowerMockIgnore("javax.crypto.*")
public class SampleTest {

@PowerMockIgnore ("javax.crypto. *") Is compatible with ClassCastException that does not normally occur when using javax.crypto.Cipher with a combination of Powermock and Spring. It's a memorandum, so make a note.

If you use PowerMock, you may not be able to execute only a specific test method, so if that happens, add @Ignore instead of @Test except for the test method you want to check. PowerMock increases what you can do, but it also increases what you can't do, so sometimes I think it's a little na ... …… But it's great to be able to mock static methods… when you want to cause an error with the standard API called in the test target…

I would like to write about Rule Chain as well.

Recommended Posts

[Java] JUnit4 test case example
[Java] Test private methods with JUnit
Primality test Java
Java automated test implementation with JUnit 5 + Gradle
[Java] How to test for null with JUnit
[CircleCI 2.0] [Java] [Maven] [JUnit] Aggregate JUnit test results with CircleCI 2.0
Java automated test implementation with JUnit 5 + Apache Maven
Java Enum utilization example
Try JUnit test launch
Java JUnit brief note
Java Unit Test Library-Artery-Sample
Java Servlet LifeCycle Example
Creating a test case
Unit test with Junit.
Get the name of the test case in the JUnit test class
Test Web API with junit
Test private methods in JUnit
Fastest Primality Test C # Java C ++
Java test code method collection
2018 Java Proficiency Test for Newcomers-Basics-
Test private methods in JUnit
[JUnit] Test the thrown exception
I investigated Randoop, a JUnit test class generator for Java
[Java] I want to test standard input & standard output with JUnit
Implement Table Driven Test in Java 14
Execution environment test after Java installation
Java Unit Test Library-Artery-ArValidator Validates Objects
Encoding and Decoding example in Java
Java Unit Test Library-Artery-Current Date Judgment
JUnit 5 parameterization test is super convenient
Java SE Bronze exam test content
Introducing Java tips GreenMail to Junit5
Output JUnit test report in Maven
Java Unit Test Library-Artery / JUnit4-Array Equivalence
Test Spring framework controller with Junit