I am new to Java and I have been struggling with this issue where the test with coverage fails on a method that takes input (via Scanner). The strange thing about it is that, the exact same test passes if I only run the particular test method from intellij.
I have been able to replicate it to some extent in this simple example below –
InputName.java
import java.util.Scanner; public class InputName{ private static Scanner scanner = new Scanner(System.in); public static void main(String[] args) { String name = inputName(); System.out.println("You entered - " + name); } public static String inputName() throws IllegalArgumentException { System.out.println("Please enter your name with up to 8 characters: "); String name = scanner.nextLine(); Integer nameLength = name.length(); if (nameLength > 8){ throw new IllegalArgumentException("Name length is more than 8 characters long"); } else { return name; } } }
InputNameTest.java
import org.junit.Test; import java.io.ByteArrayInputStream; import static org.junit.Assert.assertEquals; public class InputNameTest{ @Test public void testInputName() { // Scanner scan = new Scanner(System.in); // InputStream sysInBackup = System.in; System.setIn(new ByteArrayInputStream("JohnDoen".getBytes())); assertEquals("JohnDoe", InputName.inputName()); // System.setIn(sysInBackup); } @Test (expected = IllegalArgumentException.class) public void testIllegalArgumentException() { // InputStream sysInBackup = System.in; System.setIn(new ByteArrayInputStream("JohnnyDoen".getBytes())); InputName.inputName(); // System.setIn(sysInBackup); } }
Directory structure –
Check |-- src |- main | |- InputName.java |- tests |- InputNameTest.java
Test uses Junit4.
Here, only the second test might fail for you. However in my actual project, both tests fail when run as coverage (maybe because the test tests other methods that use Scanner before this method). But both pass when run individually.
Failing when whole test run with coverage – Passing when running only that test method –
Advertisement
Answer
You UnitTest is brittle because of a bad design. Your class under test is S.T.U.P.I.D. code.
It is a common misconception that methods in “Utility classes” should be static
. Infact they should not, especially if those methods are not pure functions but need some dependencies as your code does.
The proper solution to that problem would be to remove the static
keyword from the method an pass an instance of the Scanner class as a constructor parameter instead of instantiating it inside the class InputName
itself:
import java.util.Scanner; public class InputName{ private final Scanner scanner; InputName(Scanner scanner){ this.scanner = scanner; } public static void main(String[] args) { String name = new InputName(new Scanner(System.in)).inputName(); System.out.println("You entered - " + name); } public String inputName() throws IllegalArgumentException { System.out.println("Please enter your name with up to 8 characters: "); String name = scanner.nextLine(); Integer nameLength = name.length(); if (nameLength > 8){ throw new IllegalArgumentException("Name length is more than 8 characters long"); } else { return name; } } }
The your test would change to this:
import org.junit.Test; import java.io.ByteArrayInputStream; import static org.junit.Assert.assertEquals; public class InputNameTest{ @Test public void testInputName() { assertEquals("JohnDoe", new InputName(new Scanner(new ByteArrayInputStream( "JohnDoen".getBytes()))).inputName()); } @Test (expected = IllegalArgumentException.class) public void testIllegalArgumentException() { assertEquals("JohnnyDoe", new InputName(new Scanner(new ByteArrayInputStream( "JohnnyDoen".getBytes()))).inputName()); } }
No need to mess around with System.in
Of course, using a mocking framework (like Mockito) to create a test double of the Scanner
class would be even better.