简体   繁体   中英

Java Writing unittest for exiting a program when user type quit in the console

I found really hard to write unit test for this method, it basically exits the program when user types a quit command.

SytemExit class:

public class SystemExit {
    public void exit(int status) {
        System.exit(status);
    }
}

My static method:

public static void exitWhenQuitDetected() {
final SystemExit systemExit = new SystemExit();
final String QUIT = "quit";
String line = "";
try {
    final InputStreamReader input = new InputStreamReader(System.in);
    final BufferedReader in = new BufferedReader(input);
    while (!(line.equals(QUIT))) {
        line = in.readLine();
        if (line.equals(QUIT)) {
            System.out.println("You are now quiting the program");                  
            systemExit.exit(1);
        }
    }
} catch (Exception e) {
    System.err.println("Error: " + e.getMessage());
}
}   

Something is not quite right here as I am struggling to unit test the method exitWhenQuitDetected (I am using Mockito for mocking). How would I mock the InputStreamReader and verify the SystemExit.exit method gets called when it sees a quit? Can you shed some lights on this please? Thanks.

Added the test I am working on at the moment, it's not working.

    @Test
@Ignore
public void shouldExitProgramWhenTypeQuit() {
    String quit = "quit";           
    SystemExit systemExit = mock(SystemExit.class);
    try {
        BufferedReader bufferedReader = mock(BufferedReader.class);
        when(bufferedReader.readLine()).thenReturn(quit + "\n");
        SomeClass.exitWhenQuitDetected();
        verify(systemExit, times(1)).exit(1);
    } catch (IOException e) {           
        e.printStackTrace();
    }       
}

You should include the PowerMockito Jars into your project rather than just vanilla Mockito. The Powermock library is designed for mocking Static and/or Final classes and methods.

The following this blog post contains example code describing a similar scenario to yours.

Essentially you require a test class similar to this...

@RunWith(PowerMockRunner.class)
@PrepareForTest({System.class, ClassToTest.class})
public class SystemExitTest {

    @Test
    public void shouldCallSystemExit() {

        PowerMockito.mockStatic(System.class);

        ClassToTest.methodToTest();

        PowerMockito.verifyStatic();

        System.exit(0);

        System.out.println("If this message displays them System.exit() was mocked successfully");
    }    
}

Given this simple implementation class...

public class ClassToTest {

    public static void methodToTest() {
        // do some stuff
        System.exit(0);
    }
}

There is no real way to test you SystemExit class since exercising it will cause the JVM to exit. You might be able to do something with a SecurityManager which detects and rejects the System.exit() , but that's going to be a whole lot of work to test a single line of code.

You've done the right thing - you've pulled the functionality into a small class. If I were you, I would put an interface on it and inject it via the interface into the parsing code. Then in your test you can inject a mock and test that your parsing code calls the exit() method on the mock with the right exit code.

The code in the SystemExit class is small and self-contained enough to look at and reason about without testing, IMHO.

You've done 90% of the work already, by placing the actual exiting code off in a separate class with no logic of its own. Your difficulty is caused by your use of a static method.

I would advise making the exitWhenQuitDetected not static. Put it in a class that you can instantiate when you need it, and that you can create with a mocked SystemExit . Something like this.

public class SomeClass{
  private final SystemExit exiter;
  private final static String QUIT = "quit";
  public SomeClass(){
    this(new SystemExit());
  }

  SomeClass(SystemExit exiter){
    this.exiter = exiter;
  }

  public static void exitWhenQuitDetected() {    
    String line = "";    
    try {    
      final InputStreamReader input = new InputStreamReader(System.in);    
      final BufferedReader in = new BufferedReader(input);    
      while (!(line.equals(QUIT))) {    
        line = in.readLine();    
        if (line.equals(QUIT)) {    
          System.out.println("You are now quiting the program");                      
          exiter.exit(1);    
        }    
      }    
    } catch (Exception e) {    
      System.err.println("Error: " + e.getMessage());    
    }    
  }       

  // ...
}

Then, in your test, you can make a mock of SystemExit , and use the package-private constructor of SomeClass to create an object that will use your mock as its exiter . You can then run your test, and verify on your mock SystemExit .

Plenty of technical solutions were offered. I would like to point out another perspective:

This code should not really be unit tested.

The best gain you get out of unit tests is when applying them to complex business code, especially with a lot of branching.

In cases where the code is trivial, I would advise against writing unit tests around it since it simply just does not have a high enough return of investment. your case actually exacerbates my claim, think of the amount of effort it takes you to test simple code such as this and compare it with the gain.. does this really worth the effort. Does this really make you trust your own code more?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM