Skip to content
Advertisement

Java Test with coverage fails on methods that use Scanner, but the same test passess successfully if only the particlar test method is executed

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 – enter image description here Passing when running only that test method – enter image description here

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

Advertisement