nogard
nogard

Reputation: 9706

How to run JUnit test in separate processes?

I have 2 integration tests implemented with JUnit. Both of the tests perform calls to the remote server and the server target is configured by environment variable:

System.setProperty("property", "value1");

The tricky thing is that those properties must be different for 2 tests. If I set the environment variable for each unit test it does not work, because the middleware that we are using caches the property value on first call and does not evaluate it anymore (for the second test).

I believe the solution could be to run those unit tests in separate processes. I saw similar discussion here, but maybe there is more elegant way fo doing this with JUnit4? This problem looks pretty common. Or maybe there are another ways to run unit tests with different configuration?

Thanks in advance for any advices.

Upvotes: 5

Views: 5842

Answers (5)

Igor Maznitsa
Igor Maznitsa

Reputation: 873

I needed to isolate junit tests during work with some legacy code so I developed small maven plugin Jute which allows to start every Junit test method in separated external process, I have published it in GitHub and in maven central, may be the plugin would be appropriate solution for your question, also the plugin allows to start such tests with different JVMs

Upvotes: 3

rrufai
rrufai

Reputation: 1495

Using the ProcessBuilder API, you could alter your environment variables just before launching each process. Here's a code snippet how you might do that.

 @Test
 void testVariant1(){
      String [] commandArray = 
        {"java", "-cp", "your/class/path", "org.mydomain.myClass", ...};
      Map<String, String> envVarsForThisTest = new HashMap<String, String>();
      envVarsForThisTest.put("newProperty", "value1");
      List<String> staleVars = new List<String>();
      stateVars.add("oldProperty");
      File workingDir = new File("myDir"));
      Process p = runVariant(
                commandArray, envVarsForThisTest, staleVars, workingDir);
      Assert.assert(p.waitFor(), 0);
      checkAssertions(p.getOutputStream(), p.getErrorStream());
 }

 void checkAssertions(OutputStream output, InputStream errorStream){
      // where you'll check the return values against your expectations
 }

 void runVariant(String commandArray[], 
           Map<String, 
           String> newEnvironmentVariables, 
           List<String> environemntVariablesToRemove, 
           File workingDirectory){
      ProcessBuilder pb = new ProcessBuilder(commandArray);
      Map<String, String> env = pb.environment();
      for(Map.Entry<String, String> entry : newEnvironmentVariables){
           env.put(entry.key(), entry.value());
      }
      for(String staleVariable : environemntVariablesToRemove){
           env.remove(staleVariable);
      }
      pb.directory(workingDirectory);
      return pb.start();
 }

Upvotes: 0

Philipp
Philipp

Reputation: 69663

Unit tests should generally not depend on external resources like a remote server. Otherwise you are not testing the unit but the availability and correctness of the resource it relies on.

So how can such units be tested?

You usually do this with mock objects. You encapsulate the calls to the external resource in an object and pass this object to the unit you want to test (that's called dependency injection). In your unit test you don't pass a real server abstraction object. You are instead passing a mock object which implements the same interface or extends the server abstraction class. The mock object does not really query the server. Instead it immediately returns the value the server should return.

This likely requires some refactoring of your code. But it allows your unit-test to work without relying on an external resource. It also makes the test much, much faster which reduces the overall execution time of your test suit. This allows you to perform unit tests much more frequently

Upvotes: -2

AlexR
AlexR

Reputation: 115328

It depends on how are you running JUnit.

If you want to be able to run it either from IDE or from build implement your custom Runner and mark test case using annotation RunWith(MyRunner.class).

If you are using maven and it is enough for you to be able to run test from there use <forkMode>always</forkMode> into maven-surefire-plugin definition.

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.11</version>
            <configuration>
                <systemProperties>
                    <systemProperty>
                        <name>panpwr.conf.dir</name>
                        <value>${basedir}/conf-test</value>
                    </systemProperty>
                </systemProperties>
                <forkMode>always</forkMode>

Upvotes: 3

Peter Lawrey
Peter Lawrey

Reputation: 533492

It is considered bad practice to have state which cannot be reset between tests. While code which cannot be tested easily is common, the solutions are not simple.

I would consider resetting the cached value even if you have use reflections to do it. Exactly what needs to be reset depends on the internal representation of the library.

Upvotes: 3

Related Questions