34

I wrote a class which takes input from console and arguments in main method. The main method calls different methods for different console inputs and it calls different function for different arguments. So i want to test this main method with Junit by mimicking these inputs from a file. how can i do it? Is there any special provision in junit to test the main method of the class?

user2719152
  • 939
  • 3
  • 11
  • 20
  • 4
    Couldn't you test the processing methods instead ? This would be far more efficient. – Arnaud Apr 01 '16 at 06:54
  • i have tested methods they are working fine but i want to test main method. – user2719152 Apr 01 '16 at 06:55
  • 2
    The `main` method of a Java program is just that ... a method! It can be called by any other method, too (especially from a test method). So what problem do you have with that? – Seelenvirtuose Apr 01 '16 at 06:56
  • Actually i am taking same approach and i am facing problem to differentiate between arguments and command line inputs. Since main takes String[] only and it need to support for both console input and arguments. – user2719152 Apr 01 '16 at 07:02
  • This site has a search function. There are numerous questions about how to do this. – Raedwald Apr 01 '16 at 08:29
  • 1
    See also http://stackoverflow.com/questions/31016037/using-junit-to-test-a-main-method-that-requires-simulation-of-keyboard-input-wit – Raedwald Apr 01 '16 at 08:36

3 Answers3

25

To provide the input from a file, make a FileInputStream and set that as the System.in stream. You'll probably want to set the original back after the main method has finished to make sure anything using it later still works (other tests, JUnit itself...)

Here's an example:

@Test
public void testMain() throws IOException {
    System.out.println("main");
    String[] args = null;
    final InputStream original = System.in;
    final FileInputStream fips = new FileInputStream(new File("[path_to_file]"));
    System.setIn(fips);
    Main.main(args);
    System.setIn(original);
}

In your actual code you'll want to handle any IOExceptions and use something better than a full path to the file (get it via the classloader), but this gives you the general idea.


EDIT:

A couple years and some more wisdom later, I'd have to agree with Michael Lloyd Lee mlk's answer as the better approach and what should be preferred if possible. In the class containing your main method (or even a separate class) there ought to be a method that accepts an arbitrary InputStream and the arguments array. It could be a static method. The main method then simply calls it with the System.in stream as argument.

public class Main {
    public static void main(String[] args) {
        start(System.in, args);
    }
    public static void start(InputStream input, String[] args) {
        // use input and args
    }
}

Or alternatively you could have a class with a constructor accepting the arguments array to create a properly configured instance and then call an instance method on it that accepts an InputStream, like so:

public class Main {
    public static void main(String[] args) {
        Bootstrapper bootstrapper = new Bootstrapper(args);
        bootstrapper.start(System.in);
    }
}

public class Bootstrapper { // just some name, could be anything else

    // could have some instance fields here...
    
    public Bootstrapper(String[] args) {
        // use the args to configure this instance, set fields, get resources...
    }
    
    public void start(InputStream input) {
        // use input
    }
}

Whatever fits the requirements and program design best. In both cases you end up with something that can be unit-tested with an InputStream obtained from a file. A test doing a simple static method call to Main.start, or a test creating a Bootstrapper instance and then calling its start method. Now your testing is no longer dependent on the System.in stream. The main method does minimal work and is simple enough for you to just trust its correctness without a separate test. At some point a piece of code becomes so trivial that testing it is equivalent to distrusting the compiler. And an issue in these main methods would become apparent very soon as it's the one method you know will always be called. A main method that does too much or is complex usually points to a lack of modularization in the code.

I leave the original answer in because it does offer a solution in case you really can't get past the use of System.in and testing the main method. That's why I wrote it in the first place, because I couldn't be certain about the asker's constraints. Sometimes someone just wants a straight answer to their exact question because the better approach is not available; you're not allowed to change the code, or refactoring it is too much effort and there's no budget, or some library is used that is hardcoded to use the system streams etc. Note that the original answer is less robust anyway. If the tests are run in parallel instead of sequentially, swapping out a system stream could cause unexpected and inconsistent failures.

G_H
  • 11,739
  • 3
  • 38
  • 82
  • But fips would read only first line. how can i move to next line in the file? – user2719152 Apr 01 '16 at 11:16
  • 1
    Works fine with multi-line files for me, when I'm using a `Scanner`. I guess it would depend on your main method? – G_H Apr 01 '16 at 11:43
  • What package do the Main class belongs to? – acarlstein Jan 14 '19 at 17:43
  • @acarlstein Whatever package you chose to put the Main class into. Assuming it's a class you wrote yourself. In sample code that's this short I leave out imports if they're too obvious and package names if they're not important for what's being explained. – G_H May 29 '21 at 01:28
11

IMO the best way to test the main method is by having the main method do absolutely nothing but set up the world and kick it off. This way one simple integration test gives the answer "has the world been set up".

Then all the other questions become much easier to answer.

The main method calls different methods for different console inputs and it calls different function for different arguments.

It should not, it should call someService.somemethod(args). This service gets tested like any other.

So i want to test this main method with Junit by mimicking these inputs from a file. how can i do it?

Either some form of fakes injected in or the use of the TemporaryFolder JUnit rule.

Michael Lloyd Lee mlk
  • 14,561
  • 3
  • 44
  • 81
  • I often see comments like this where "the right thing" is suggested. And while I always agree, remember that we rarely know the full extent of the situation. Sometimes you're tasked with testing someone else's crummy code or legacy cruft you can't/aren't allowed to change. – G_H Apr 01 '16 at 10:52
  • 6
    While it is true that "the right way" in this case may well be to not do it "the right way", I think it is important to have the right way on the list of answers. If a new developer stubbles across this page and sees "the wrong way" without any pointers to "the right way" they may well leave with the wrong idea. – Michael Lloyd Lee mlk Apr 01 '16 at 14:11
  • Can't argue with that. It's pretty likely that if he's testing the main method, he wrote it. – G_H Apr 01 '16 at 14:22
10

You can call main method from junit test like this:

YourClass.main(new String[] {"arg1", "arg2", "arg3"});

But since main method is void and does not return anything, you should test object that changed after main invocation;

Here is Link How do I test a method that doesn't return anything?

Davit Mumladze
  • 918
  • 2
  • 9
  • 25
  • yes we can call like this but after doing this main function changes its behavior.... and i don't want to change it. – user2719152 Apr 01 '16 at 09:04
  • @Test public void mainTest() throws Exception{ String programArgs = "myArgs --reuseIndex --dateRange "+todayDate.get(); MainClass.main(programArgs.split("\\s+")); } – Mandy Oct 01 '19 at 10:33