0

Hi this is the first time im trying out unit testing in java using eclipse.

So when i test my class, it requires user input. Lets say a command called "add hello", so that it will create a new textfile and add the word "hello" to it. Then i want to test a function called getAllLines which returns "hello" and compare it with assert.

My main problem is how to simulate user input to console via junit test. This is what i tried but its not working..

private void performUserInput(String strInput){
    ByteArrayInputStream in = new ByteArrayInputStream(strInput.getBytes());
    System.setIn(in);
}

private void releaseUserInputToSystem(){
    System.setIn(System.in);;
}

@Test
public void testSearchingInEmptyFile() {

    TextBuddy textBuddy = new TextBuddy("file.txt");

    textBuddy.run();
    performUserInput("add little brown fox");
    releaseUserInputToSystem();

    assertEquals("little brown foxx", "asd");
}

It seems to me like the code never reaches assert.

edit---------------------------------------------- After debugging, its getting stuck here

private String[] getCommandAndArgs(){
    String[] splitCommand = scanner.nextLine().split(" "); //<<STUCK HERE
    printNewLine();
    return splitCommand;
}
RStyle
  • 875
  • 2
  • 10
  • 29

4 Answers4

1

With the Unit-Test you should rather test single Methods (units) of your TextBuddy Class. You probably have a method which check the commands (add, remove, whatever you have). Write Unit Tests for those e.g.:

@Test
public void testCommandAdd() {
    TextBuddy  tb = new TextBuddy ();
    int command tb.parseCommand("add hello");
    assertThat(command,is(TextBuddy.ADD));
}
@Test
public void testCommandRemove() {
    TextBuddy  tb = new TextBuddy ();
    int command tb.parseCommand("remove hello");
    assertThat(command,is(TextBuddy.REMOVE));
}

Then write tests for each command, e.g. that a file was written/deleted whatever:

@Test
public void testWriteFile() throws SQLException {
    TextBuddy  tb = new TextBuddy ();
    tb.writeFile("file.txt", "hello");
    File f = new File("file.txt");
    String content = readFile(f);
    assertThat(content,is("hello"));
}

Always test single units of your program with small Unit Tests. Later you can write bigger Tests that check if your hole Program works.

If you don't want to expose your Methods with the public modifier, you can still test them - the simplest way is makeing them package-private and have your tests the same package (they can and should be in a differnet src-folder) e.g. for a Class with the package com.yourpackage like this

src/com/yourpackage/YourClass.java

You could store your test in

test/com/yourpackage/YourClassTest.java

Then you can access package-private Methods.

Or you use Reflection to access and test a private Method, see here and here

Community
  • 1
  • 1
hinneLinks
  • 3,673
  • 26
  • 40
0

A unit test is all about automation and should not rely on user input. If you want to test a user input mechanism, you should write a test that simulates a user entering the input to be tested (e.g. using Selenium for front end testing a web application). If you want to test behavior based on input, you should contruct tests that enter all possibilities that shall be tested automatically and validate the respective correct behavior of your application/program/functionality.

If TextBuddy.run() is specified to read something from System.in, it looks like a good idea to redirect System.in before the call to run():

performUserInput("add little brown fox");
textBuddy.run();
releaseUserInputToSystem();

But maybe you could improve your TextBuddy to be more testable by adding the input stream to read from as a parameter to the run method:

public void run(InputStream in) {
    // use parameter in instead of System in...
}

/**
 * Convenience method to run with System.in
 */ 
public void run() {
    run(System.in);
}

In your test, call run(InputStream):

ByteArrayInputStream in = new ByteArrayInputStream(strInput.getBytes());
textBuddy.run(in);

You could also add an out parameter to be able to redirect the output of your tested method.

Lars Gendner
  • 1,816
  • 2
  • 14
  • 24
0

I agree with @LarsGendner you should mock (do some simulate or fake things) for the input task. You can use some technique that provides input to coverage at least for some part of this code (for example TextBddy). In this case, I can suggest three approaches.

  • static inputs use this approach if you need to have some text samples derived from empirical testing. You can use this form model some typical behavior from existing users;
  • random string generator use this approach to do string generation from scratch (you can combine this approach with static input)
  • framework (or library) to extend jUnit capability in towards of data mining or artificial intelligence such as genetics algorithms, neural networks and so on.

In my onion the last technique is more sophisticate. So, you should evaluate and perform a thread-off to decide which parts should be testing and which should be the code coverage that you need. Notice, that mocking some input task according to human behavior it is not trivial task.

e2a
  • 952
  • 7
  • 18
0

Regarding your original code, when you execute textBuddy.run(); it will lock expecting user input, so the next line (performUserInput("add little brown fox"); which does provide the input it is waiting for) is never executed.

So, to make that code work, you should call performUserInput("add little brown fox"); before calling textBuddy.run():

@Test
public void testSearchingInEmptyFile() {
    TextBuddy textBuddy = new TextBuddy("file.txt");

    performUserInput("add little brown fox"); // changed these two lines
    textBuddy.run();                          // switched their order
    releaseUserInputToSystem();

    assertEquals("little brown foxx", "asd");
}

Another approach is to use threads: execute textBuddy.run() in one thread and performUserInput("add little brown fox") in other. It is probably best to avoid this, though, as threads may make your tests way more difficult to maintain.


Full demo code:

public class TextBuddyTest {

    private InputStream performUserInput(String strInput) {
        InputStream originalSystemIn = System.in;
        ByteArrayInputStream in = new ByteArrayInputStream(strInput.getBytes());
        System.setIn(in);
        return originalSystemIn;
    }

    private void restoreSystemInputStream(InputStream originalSystemIn) {
        System.setIn(originalSystemIn);
    }

    @Test
    public void testSearchingInEmptyFile() {
        // setup
        InputStream defaultSystemIn = performUserInput("add little brown fox");

        TextBuddy textBuddy = new TextBuddy("file.txt");
        // execute
        textBuddy.run();
        // verify
        assertEquals("[add, little, brown, fox]", textBuddy.getWhatWasInput());
        // teardown
        restoreSystemInputStream(defaultSystemIn);
    }

    static class TextBuddy { // created for this demo
        Scanner scanner = new Scanner(System.in);
        String whatWasInput;
        public TextBuddy(String s) { }
        public void run() { this.whatWasInput = Arrays.toString(getCommandAndArgs()); }
        private String[] getCommandAndArgs() { return scanner.nextLine().split(" "); }
        public String getWhatWasInput() { return whatWasInput; }
    }

}
acdcjunior
  • 132,397
  • 37
  • 331
  • 304