5

I have a class to which I must pass 2 arguments through its main method, if passed less than 2 args, it displays a system error message. I wrote a unit test for the main method here, when I run the test, it stops at "running" (shows neither pass nor fail). Please suggest.

Example.java

public class Example 
{
    private static String str1   = null;
    private static String str2   = null;

    public static void main(String[] args)
    {
        if( args.length != 2 )
        {
            call();
        }

        Example ex = new Example(args[0], args[1]);
        ex.getData();
    }

    public Example(String str1, String str2)
    {
        Example.str1 = str1;
        Example.str2 = str2;
    }

    public void getData(){
        System.out.println("Name is: "+str1);
        System.out.println("City is: "+str2);
    }

    private static void call()
    {
        System.err.println("Usage: String1 String2");
        System.err.println("Where: ");
        System.err.println("       String1 - Name");
        System.err.println("       String1 - City");
        System.exit(1);
    }   
}

ExampleTest.java

public class ExampleTest {
    @Test
    public void testPassingWrongNumberOfInputs() {
        StringBuffer sb = new StringBuffer();
        sb.append("Usage: String1 String2")
        .append("Where: ")
        .append("       String1 - Name")
        .append("       String1 - City");

        String expectedErrorMessage = sb.toString();

        ByteArrayOutputStream outContent = new ByteArrayOutputStream();
        System.setErr(new PrintStream(outContent));
        String[] args = {"one"};
        Example.main(args);

        assertEquals(expectedErrorMessage, outContent.toString());
    }
}
devlperMoose
  • 305
  • 1
  • 3
  • 14
  • 2
    What is the purpose? A main method is rather *un*-unit. The idea of 100% converage is rather problematic: it tends to make tests somehow weaker because people start to invent non-sense tests simply to get 100% converage. – Willem Van Onsem Jan 04 '16 at 22:55
  • 1
    Your test is kind of funny (not meant to be rude). It says: please test this method which will kill your running environment and then we will see, if you're alive enough to tell me if this test was good or not. In other words: recheck what `System.exit(1);` does. – Tom Jan 04 '16 at 22:57
  • 2
    The `System.exit()` call may be related to the fact that your code stops... – Oliver Charlesworth Jan 04 '16 at 22:58
  • 1
    The coding review team in our company asked me to add unit tests for the main method as well. – devlperMoose Jan 05 '16 at 19:22

4 Answers4

2

How about the following:

class TestingSecurityManager extends SecurityManager {
  @Override public void checkExit(int status) {
    throw new SecurityException();
  }
}

Then in your test...

public class ExampleTest {
    @Test
    public void testPassingWrongNumberOfInputs() {
        StringBuffer sb = new StringBuffer();
        sb.append("Usage: String1 String2")
        .append("Where: ")
        .append("       String1 - Name")
        .append("       String1 - City");

        String expectedErrorMessage = sb.toString();

        ByteArrayOutputStream outContent = new ByteArrayOutputStream();
        System.setErr(new PrintStream(outContent));
        String[] args = {"one"};

        TestSecurityManager sm = new TestSecurityManager ();
        System.setSecurityManager(sm);

        try {
            Example.main(args);
            //should throw
            fail("Should have thrown exception");
        } catch (SecurityException se) {
        }

        assertEquals(expectedErrorMessage, outContent.toString());
    }
}
emory
  • 10,725
  • 2
  • 30
  • 58
Jose Martinez
  • 11,452
  • 7
  • 53
  • 68
  • 1
    Why not pass the status into the exception like `new SecurityException(Integer.toString(status)` then in the catch clause you can `assertEquals("1", se.getMessage())`? Perhaps you should move the `setSecurityManager` code to a setup method and create a teardown method that restores the security manager. The same VM might be used for multiple tests. This is very good. – emory Jan 05 '16 at 04:35
  • Good ideas. All possible. – Jose Martinez Jan 05 '16 at 13:12
  • @JoseMartinez I tried your code, but the test is failing with the following error: org.junit.ComparisonFailure: expected:<[Usage: String1 String2Where: String1 - Name String1 - City]> but was:<[access: access allowed ("java.io.FilePermission" "C:\codebase\jboss\TestProject\bin\com\usaa\example\Example.class" "read") access: access allowed ("java.io.FilePermission" "C:\codebase\jboss\TestProject\bin\com\usaa\example\Example.class" "read") Usage: String1 String2 Where: String1 - Name String1 - City ]> – devlperMoose Jan 05 '16 at 19:45
  • @JoseMartinez I figured out where am i getting those access allowed messages from. Its because I added -Djava.security.debug=access,failure to VM args. I removed them but still getting the comparison failure erro. Please suggest. org.junit.ComparisonFailure: expected:<...age: String1 String2[ Where: String1 - Name String2 - City]> but was:<...age: String1 String2[ Where: String1 - Name String2 - City ]> – devlperMoose Jan 05 '16 at 19:58
  • You have an extra space after the second "City". Copy one line under the other in a text editor, and you can compare them easily. – GregT Jun 07 '17 at 08:02
2

I finally was able to write the unit test as shown in the following. I only tested if the method is hitting System.exit(1) code or not.

public class ExampleTest {
    private SecurityManager m;
    private TestSecurityManager sm;

    @Before
    public void setUp() 
    {
        m = System.getSecurityManager();
        sm = new TestSecurityManager ();
        System.setSecurityManager(sm);
    }

    @After
    public void tearDown()
    {   
        System.setSecurityManager(m);
    }

    @Test
    public void testPassingWrongNumberOfInputs() {
        try {
            Example.main(new String[] {"one"});
        } catch (SecurityException se) {
            assertEquals("1", se.getMessage());
        }
    }
}

class TestSecurityManager extends SecurityManager {
    @Override 
    public void checkPermission(Permission permission) {            
        if ("exitVM".equals(permission.getName())) 
        {
            throw new SecurityException("System.exit attempted and blocked.");
        }
    }
    @Override 
    public void checkExit(int status) {
        throw new SecurityException(Integer.toString(status));
    }
}
emory
  • 10,725
  • 2
  • 30
  • 58
devlperMoose
  • 305
  • 1
  • 3
  • 14
0

Remove the System.exit(1) call, you don't need it. Your app will exit after main() completes anyway without an unneeded call to explicitly terminate the VM. This call is most likely causing your JUnit to stop executing before you get to your assertEquals statement, because you just told the VM to quit.

Kevin Hooke
  • 2,583
  • 2
  • 19
  • 33
  • Then you suggest him to remove `System.exit(1)`, then you should also tell him how to avoid the `ArrayIndexOutOfBoundsException` which he can get now. – Tom Jan 04 '16 at 23:11
  • 1
    @Tom Fixing the `ArrayOutOfBoundsException` should be trivial. However, power users tend to use exit codes to indicate the status of a program. If you do not explicitly set it to 1, it will be 0 - falsely indicating success. – emory Jan 04 '16 at 23:19
  • 1
    Whether or not 'power users' return an exit code is not the issue. Calling System.exit() causes the VM to prematurely quit. Your code under test and your JUnit are executing on the same VM. if your code under test quits the VM, then your JUnit quits too. It's never a good practice because if this code was executed on an app server then you have just brought your app server down. That's never a good thing in production. – Kevin Hooke Jan 05 '16 at 00:41
  • If you're looking for a mechanism to return a result from a method, then change your method signatures to specify a return type, and use return to return a value (this is probably what you're looking for - System.exit() is definitely not the way to do what you're looking for) – Kevin Hooke Jan 05 '16 at 00:44
  • I need System.exit(1), because this Example class will be called from a script in the production environment. Script doesn't understand the language of "return;". – devlperMoose Jan 05 '16 at 19:21
  • Ok, well then maybe you need to rethink your testing approach. You understand calling System.exit() in the code being tested is causing the JVM to exit for BOTH the running code being tested AND the JUnit testing the code? This is why you are never seeing your JUnit complete. You told the JVM to exit... and it did. – Kevin Hooke Jan 05 '16 at 22:10
  • See http://stackoverflow.com/questions/309396/java-how-to-test-methods-that-call-system-exit – Kevin Hooke Jan 07 '16 at 20:55
0

Rename the main method, and add a return value, so you can test it. Call this new method from main.

GregT
  • 1,039
  • 4
  • 14
  • 34