[Java] Test private methods with JUnit

Call a private method as a test

Previously in What you should be aware of when writing Java, ** private methods can be called by reflection, so access modifiers are used only for testing. I wrote that you shouldn't delete it **. So, here is an example.

If it is a field instead of a method, write it like this. → Refer / set private variables by reflection

2018/12/26 Added "Call private constructor"

Pros and cons of testing per private method

We are aware that there is an opinion that "it should be called from the public method to test the private method consistently". However, depending on the project, there are many cases that include a method-based test = private method. It's a rule. Everyone writes the test code accordingly. Even if you break it, you will only be told to rewrite it. In some cases, there is only one public method in the class in the business logic. In such a case, the test is also difficult. It is easier to write a test for a public method after guaranteeing the processing for each private method with a test. That's why the pros and cons of testing per private method are out of scope.


The sample code omits Javadoc and some kettles. I have confirmed the operation so that it works with copy and paste, but it is just a sample code and reference.

Class to be tested

Sample.java


public class Sample {
	private String strValue = null;

	public Sample(String value) {
		this.strValue = (value == null) ? "" : value;
	}

	/** 5.Call private constructor*/
	private Sample() {
	}

	/** 1.Calling a non-static method*/
	private boolean equals(String value) {
		return this.strValue.equals(value);
	}

	/** 2.Call static method*/
	private static boolean isEmpty(String value) {
		return (value == null || "".equals(value)) ? true : false;
	}

	/** 3.Call with no return value / argument*/
	private static void dispMessage() {
		System.out.println("Hello world!");
	}

	/** 4.Check for exceptions*/
	private void setValue(String value) {
		if (value == null || value.isEmpty()) {
			throw new IllegalArgumentException("argument is empty.");
		}
		this.strValue = value;
	}
}

1. Calling a non-static method

doPrivateMethod is the method for calling the private method under test. In types, the argument types of the private method are stored in an array in order from the first argument and passed. args passes the arguments themselves as well. Since the method name to be tested, the type and number of arguments are specified, the test will fail if that side is changed by refactoring or the like. (InvocationTargetException occurs)

SampleTest.java


import static org.junit.Assert.*;

import java.lang.reflect.Method;
import org.junit.Test;

public class SampleTest {
	@Test
	public void test_equals() throws Exception {
		Sample testee = new Sample("test");
		assertTrue((boolean) this.doPrivateMethod(
				testee, "equals", new Class[]{String.class}, new Object[]{"test"}));
	}

	/**
	 *Non-static method call.
	 * 
	 * @param obj Tested object
	 * @param name Tested method name
	 * @param types Argument type of the method under test
	 * @param args Arguments of the method under test
	 * @return Return value of non-static method
	 */
	private Object doPrivateMethod(
		Object obj, String name, Class[] types, Object[] args) throws Exception {
		//Get information about the method under test
		Method method = obj.getClass().getDeclaredMethod(name, types);
		//Remove access restrictions to the method under test
		method.setAccessible(true);
		//Tested method call
		return method.invoke(obj, args);
	}
}

2. Call static method

doStaticPrivateMethod is the method for calling the private method under test. The ones that are vulnerable to refactoring are the same as 1. Since it is a call to a static method, no object is passed as an argument.

SampleTest.java


import static org.junit.Assert.*;

import java.lang.reflect.Method;
import org.junit.Test;

public class SampleTest {
	@Test
	public void test_isEmpty() throws Exception {
		assertTrue((boolean) this.doStaticPrivateMethod(
				"isEmpty", new Class[]{String.class}, new Object[]{null}));
	}

	/**
	 *static method call.
	 * 
	 * @param name Tested method name
	 * @param types Argument type of the method under test
	 * @param args Arguments of the method under test
	 * @return static method return value
	 */
	private Object doStaticPrivateMethod(
		String name, Class[] types, Object[] args) throws Exception {
		//Get information about the method under test
		Method method = Sample.class.getDeclaredMethod(name, types);
		//Remove access restrictions to the method under test
		method.setAccessible(true);
		//Tested method call
		return method.invoke(null, args);
	}
}

assertTrue((boolean) this.doStaticPrivateMethod("isEmpty", new Class[]{String.class}, new Object[]{null}));

If you want to pass null in a method with arguments, put it in an array. An array is required even with one argument. If you pass it as it is, it will fail.

3. Return value / Call without arguments

If the method under test is void, there is no return value. (Null even if you try to pick it up) Pass null when calling a method with no arguments. Unlike 2., it doesn't have to be an array.

SampleTest.java


import static org.junit.Assert.*;

import java.lang.reflect.Method;
import org.junit.Test;

public class SampleTest {
	@Test
	public void test_dispMessage() throws Exception {
		this.doStaticPrivateMethod("dispMessage", null, null);
	}
}

** Added from comments **

this.doStaticPrivateMethod("dispMessage", null, null);

It is not asserted because it is a sample calling method. Even in the void method test, confirmation of the processing result is indispensable. For example, if you process a DB or file, assert the result. In the case of Sample class, check whether the character string output to the console is correct. For example, like this.

//Standard output result redirection
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
//Tested call
this.doStaticPrivateMethod("dispMessage", null, null);
//Assert
assertEquals("Hello world!" + System.lineSeparator(),  out.toString());

If you want to run the sample, import ByteArrayOutputStream and PrintStream.

4. Check for exceptions

All invocation runtime errors are wrapped and thrown with ** InvocationTargetException **. Therefore, the check for exceptions thrown in the test target is performed by extracting the contents wrapped by InvocationTargetException. Also, in the case of a test that confirms that an exception is thrown, if the exception is not thrown, or if an exception other than the intended exception occurs, the test will fail on JUnit, so ** fail (explicitly ** fail ( );**call.

SampleTest.java


import static org.junit.Assert.*;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.junit.Test;

public class SampleTest {
	@Test
	public void test_setValue() throws Exception {
		try {
			Sample testee = new Sample("test");
			this.doPrivateMethod(
					testee, "setValue", new Class[]{String.class}, new Object[]{""});
			//Test failed
			fail();
		} catch (InvocationTargetException e) {
			//Exception type checking
			assertTrue(e.getCause() instanceof IllegalArgumentException);
			//Exception message check
			assertEquals("argument is empty.", e.getCause().getMessage());
		} catch (Exception e) {
			//Test failed
			fail(e.getMessage());
		}
	}
}

5. Call private constructor

Isn't it writing a private constructor with no arguments to prevent it from being called from the outside in a static class? That is the test method. It is used in projects where "100% coverage" is a condition. The comment says "Write the class name with a fully qualified name", but since the sample class this time is not packaged, only the class name is described. Normally, I think there is a package, so it will not work unless it is a fully qualified name.

SampleTest.java


import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.*;

import java.lang.reflect.Constructor;
import org.junit.Test;

public class SampleTest {
	@Test
	public void test_private_constructor() {
		try {
			//Describe the class name as a fully qualified name
			Class<?> clazz = Class.forName("Sample");
			Constructor<?>[] constructors = clazz.getDeclaredConstructors();
			constructors[1].setAccessible(true);
			Object obj = constructors[1].newInstance();
			assertNotNull(obj);
			assertThat(obj, instanceOf(clazz));
		} catch (Exception e) {
			fail(e.getMessage());
		}
	}
}

constructors[1].setAccessible(true);

Here, describe the order described in the Sample class with 0 origin. In this case, the private constructor you want to test is "1" because it is described in the second of the constructors of the Sample class. If you only have a constructor to test, "0" is fine.


Have a good Java life! If something happens, I will write it in a postscript or another article.

Recommended Posts

[Java] Test private methods with JUnit
Test private methods in JUnit
Test private methods in JUnit
How to test private scope with JUnit
Java automated test implementation with JUnit 5 + Gradle
Unit test with Junit.
[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
Mixin test cases with JUnit 5 and default methods
How to test private methods with arrays or variadic arguments in JUnit
[Java] JUnit4 test case example
Test Web API with junit
Check Java9 Interface private methods
Java methods
Java methods
Test Spring framework controller with Junit
[Java] I want to test standard input & standard output with JUnit
Control test order in Junit4 with enumeration
Java EE test (CDI / interceptor) with Arquillian
Force non-instantiation with Effective Java private constructor
JUnit 5 gradle test fails with lombok annotation
Primality test Java
Java class methods
How to test interrupts during Thread.sleep with JUnit
Build and test Java + Gradle applications with Wercker
Easy JUnit test of Elasticsearch 2018 version with embedded-elasticsearch
Build an E2E test environment with Selenium (Java)
[Java] Hello World with Java 14 x Spring Boot 2.3 x JUnit 5 ~
Test code using mock with JUnit (EasyMock center)
Visualize test methods running in TestNG with listeners
How to access Java Private methods and fields
Install java with Homebrew
Try JUnit test launch
Integration Test with Gradle
Install Java with Ansible
[Note] Methods ending with?
Compile with Java 6 and test with Java 11 while running Maven on Java 8
[JUnit 5 compatible] Write a test using JUnit 5 with Spring boot 2.2, 2.3
Comfortable download with JAVA
Java JUnit brief note
Java paid private memo
[Java] Refer to and set private variables with reflection
Java Unit Test Library-Artery-Sample
Switch java with direnv
[Rails] Test with RSpec
Test Nokogiri with Rspec.
WebAPI unit test and integration test with SpringBoot + Junit5, 4 patterns
Download Java with Ansible
Automatically test with Gauge
Load test with JMeter
Let's scrape with Java! !!
I wrote a test with Spring Boot + JUnit 5 now
Build Java with Wercker
Java encapsulation private public
Endian conversion with JAVA
Things to keep in mind when testing private methods in JUnit
How to test a private method with RSpec for yourself
I investigated Randoop, a JUnit test class generator for Java
Easy BDD with (Java) Spectrum?
Use Lambda Layers with Java