© Alexandru Jecan  2017

Alexandru Jecan, Java 9 Modularity Revealed, https://doi.org/10.1007/978-1-4842-2713-8_11

11. Testing Modular Applications

Alexandru Jecan

(1)Munich, Germany

By now, you should have a deep overview of Project Jigsaw and should be able to start using use it in your projects. But there’s one important topic we haven’t mentioned yet: unit testing. This chapter focuses on unit testing modular applications in Java 9 and the different approaches to it that you can take. In this chapter, we’ll show you some best practices for performing unit testing in Java 9 in the context of a modular application.

Suppose we have a module with classes that need to be tested. If we put the unit test classes in another module, then we need to make the unit tests be able to access types from the module being tested. This puts the strong encapsulation mechanism introduced in JDK 9 in play. A solution would be to add --add-exports flags to make the types from the module under test available to the unit tests. But this isn’t sufficient because it’s also compulsory that the types from the module under test are public. If they’re not, then the export of packages doesn’t give us the necessary level of accessibility. This is just one of the many challenges that we have to solve before performing unit testing in Java 9.

Performing unit testing in a modular application is necessary in order to achieve the desired level of software quality, and it’s even more critical here than when testing non-modular applications. Unit testing in Java 9 is a little more complicated than unit testing in versions prior to Java 9 because in Java 9 we have to assure readability between the Junit test classes and the objects under test. There can be different combinations for locations of the Junit test classes for the classes being tested. They can reside in the same module, in different modules, on the class path, or partially on the module path and on the class path. The next section goes into these scenarios in more detail.

Scenarios for Unit Testing in Java 9

As mentioned earlier, we can have different scenarios for unit testing in JDK 9, depending on the location of the Junit test classes and the objects under test. The following common scenarios may occur during unit testing in JDK 9:

  • The Junit test classes and the test objects reside in different modules.

  • The Junit test classes don’t reside in a module, but the test objects do.

  • Both Junit test classes and the test objects reside in the same module.

Each of the three scenarios has to be treated differently and requires a different approach to assure readability between the Junit test cases and the objects under test. The following subsections look at each of them in detail.

Scenario 1: Junit Test Classes and Types Under Test Are in Different Modules

Suppose that the types under test are in module A and the Junit test classes are in module B. This scenario is one of the simplest. Figure 11-1 illustrates it.

A431534_1_En_11_Fig1_HTML.gif
Figure 11-1. Junit test classes and classes under test are in different modules

First of all, we need to assure readability between the two modules. Module A should export its packages. Module B should require module A and also export its packages. In this way, module B can access the public types from module A. Module B should further require the junit automatic module in its module declaration because it’s making use of it.

To make this scenario work, be aware of one more important thing. When we run the Junit test cases from module B, we need to also add all the existing modules (including the automatic modules) using the java launcher --add-modules flag. In our case, we need to add the module B and the automatic module hamcrest.core, which is a dependency of Junit. This is basically everything we need to do in order to make this scenario work. Later in this chapter, we’ll will show an example using this scenario.

Scenario 2: Only the Types Under Test Reside Inside a Module

This second scenario is the most difficult one. In this scenario, we have the types under test in module A, but the Junit test classes aren’t residing in any module—they reside on the class path . For this, we have to use the javac -Xmodule and the java launcher --patch-module command-line options. Both options are described in detail later in this chapter. Figure 11-2 illustrates this scenario.

A431534_1_En_11_Fig2_HTML.gif
Figure 11-2. Types under test are inside a module, and Junit test cases are on the class path

First, we have to compile the Junit test classes as if it were part of module A (using the javac -Xmodule option). In this way, we make the Junit test classes part of module A. Second, we have to use the javac --add-reads command-line option to add the reading edges to Junit. This is mandatory because now module A has a dependency on Junit. Because module A doesn’t read Junit, we have to use the --add-reads command-line option to tell it to read Junit. At the same time, we also have to add the junit automatic module using the javac --add-modules option.

To make this scenario work, when we’re running the Junit test classes, we have to use the java launcher --patch-module command-line option so that we can patch module A. Therefore, we make the Junit test class part of module A at runtime using the ALL-MODULE-PATH constant. Don’t worry if it’s not clear yet how this scenario works. Later in this chapter, you’ll see this scenario at work, and we’ll explain the new command-line options used.

Scenario 3: Both Junit Test Classes and Test Under Test Reside in the Same Module

In this scenario, we have implicit readability between the types. The disadvantage in this case is related to the fact that our module needs all the test dependencies. Introducing dependencies on test libraries for every module that requires unit testing is definitely not the best solution. If we have ten modules that need to be tested, then all of them would need to add the test dependency separately.

Now that we know the most common scenarios that can occur when performing unit testing in Java 9, let’s move on to the -Xmodule command-line option of the Java compiler and the --patch-module command-line option, mentioned earlier in the second scenario. They’re used to patch modules with classes.

The -Xmodule Option

The Java compiler command-line option -Xmodule is used to compile classes for a module. Figure 11-3 describes its syntax.

A431534_1_En_11_Fig3_HTML.gif
Figure 11-3. Syntax of the javac -Xmodule command-line option

The -Xmodule option specifies that we should compile the classes as if they were part of the module <module_name>. This option is used at compile-time to inject a class into a module. It can’t be used at runtime. Using the -Xmodule option, we can make classes be part of a specific module. If the module we pass as an argument doesn’t exist, an error module not found will be thrown:

error: module not found: <module_name>
Note

It’s not possible to list more than one module. You can’t specify multiple module names to the -Xmodule command-line option.

The --patch-module Option

The JDK 9 specification states: “When testing or debugging it is sometimes useful to replace selected class files or resources of specific modules with alternate or experimental versions, or to provide entirely new class files, resources, and even packages. This can be done via the --patch-module option.”

The --patch-module option is used at both compile-time and runtime to replace the class files of a module with other class-specific class files. It can be used by the Java compiler as well as by the Java launcher. The role of the --patch-module option is to override classes inside a module. This option replaced the old -Xbootclasspath/p option, which has been removed in Java 9.

Figure 11-4 shows the syntax of the --patch-module command-line option.

A431534_1_En_11_Fig4_HTML.gif
Figure 11-4. Syntax of the --patch-module option
  • <module_name> represents the module name.

  • <file> represents the file system path name of a module definition.

  • <path_separator> represents the host platform’s path-separator character.

The module specified by <module_name> is patched with the class files existing inside the directory <file>. We can also specify a normal JAR (not a modular one) instead of the directory containing the class files.

Note

The --patch-module can be used to make the test classes part of the module at runtime. The JCP team states that it is “intended only for testing and debugging. Its use in production settings is strongly discouraged.”

The --patch-module command-line option can also be used to patch automatic modules , but it can’t be used to replace module-info.class files, as the JCP team states in the specification: “The --patch-module option cannot be used to replace module-info.class files. If a module-info.class file is found in a module definition on a patch path, then a warning will be issued and the file will be ignored.” The JCP team also tell us what happens with the packages that aren’t exported: “If a package found in a module definition on a patch path is not already exported by that module, then it will, still, not be exported. It can be exported explicitly via either the reflection API or the --add-exports option.”

Patching a Module

The following example shows how to patch a class inside a module. That means we replace a Java class file inside a module with another one. For doing this, we’ll use the javac command-line option -Xmodule and the java command-line option --patch-module. We’ll patch an existing module using the --patch-module option, and you’ll see two ways of doing that. Therefore, we create four folders:

  • The modules folder

  • The modulesLibrary folder

  • The patchModules folder

  • The patchModulesLibrary folder

We have a module com.apress.moduleA that contains a POJO class called Employee.java and another class called EmployeeImpl.java, which creates an object of type Employee and sets some properties on it. There’s also a module com.apress.moduleB that contains the public static void main(String[] args) method. This module simply creates an object of type EmployeeImpl and then calls some methods on this object. Further, we define another Java class called EmployeeImpl.java, having the same package name as the package name of the former EmployeeImpl.java class. This new class isn’t part of a module. Our intention is to replace the new class with the older one inside the com.apress.moduleA module using the command-line options -Xmodule and --patch-module described earlier.

Listing 11-1 shows the classes Employee.java and EmployeeImpl.java of the com.apress.moduleA module. The class Employee.java is inside the package com.apress.moduleA.entity.

Listing 11-1. The Classes Employee.java and EmployeeImpl.java from the com.apress.moduleA Module
// Employee.java

package com.apress.moduleA.entity;

public class Employee {

    private String firstName;
    private String lastName;
    private String department;


    public Employee() {
    }


    public String getFirstName() {
        return firstName;
    }


    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }


    public String getLastName() {
        return lastName;
    }


    public void setLastName(String lastName) {
        this.lastName = lastName;
    }


    public String getDepartment() {
        return department;
    }


    public void setDepartment(String department) {
        this.department = department;
    }


}

// EmployeeImpl.java

package com.apress.moduleA;

import com.apress.moduleA.entity.Employee;

public class EmployeeImpl {

    public Employee employee;

    public EmployeeImpl() {
    }


    public Employee createNewEmployee() {
        employee = new Employee();
        return employee;
    }


    public Employee setEmployeeInfo() {
        employee = createNewEmployee();
        employee.setFirstName("John");
        employee.setLastName("Anderson");
        employee.setDepartment("IT");
        return employee;
    }


    public void getEmployeeInfo() {
        System.out.println("Employee first name is: " + employee.getFirstName());
        System.out.println("Employee last name is: " + employee.getLastName());
        System.out.println("Employee department is: " + employee.getDepartment());
    }


}

Listing 11-2 shows the module descriptor of module com.apress.moduleA, which exports the package.

Listing 11-2. The module-info.java File of Module com.apress.moduleA
module com.apress.moduleA {
  exports com.apress.moduleA;
}

Module com.apress.moduleB imports types from module com.apress.moduleA and calls methods on an EmployeeImpl object, as shown in Listing 11-3.

Listing 11-3. The MainClass.java File of Module com.apress.moduleB
package com.apress.moduleB;

import com.apress.moduleA.*;

public class MainClass {

    public static void main(String[] args) {
        EmployeeImpl employeeImpl = new EmployeeImpl();
        employeeImpl.createNewEmployee();
        employeeImpl.setEmployeeInfo();
        employeeImpl.getEmployeeInfo();
    }
}

Listing 11-4 represents the module descriptor of module com.apress.moduleB.

Listing 11-4. The module-info.java of Module com.apress.moduleB
module com.apress.moduleB {
        requires com.apress.moduleA;
}

In the new directory com.apress.moduleA2, we define another version of the EmployeeImpl.java class, which isn’t part of any module. Listing 11-5 shows the new class having the same package name as the old one, com.apress.moduleA.

Listing 11-5. Class EmployeeImpl
package com.apress.moduleA;

import com.apress.moduleA.entity.Employee;

public class EmployeeImpl {

    public Employee employee;

    public EmployeeImpl() {
    }


    public Employee createNewEmployee() {
        employee = new Employee();
        return employee;
    }


    public Employee setEmployeeInfo() {
        employee = createNewEmployee();
        employee.setFirstName("Andrew");
        employee.setLastName("Lopez");
        employee.setDepartment("Big Data");
        return employee;
    }


    public void getEmployeeInfo() {
        System.out.println("Employee first name is: " + employee.getFirstName());
        System.out.println("Employee last name is: " + employee.getLastName());
        System.out.println("Employee department is: " + employee.getDepartment());
    }
}

We changed only the first name, last name, and department in this last example. Now that we have the code , we begin the process of replacing the EmployeeImpl.java from the last listing with the one from module com.apress.moduleA. First, we compile both existing modules but exclude the directory com.apress.moduleA2, because it’s not a module. The following command does this, making use of the grep -v command in order to exclude the directory com.apress.moduleA2 from the compilation:

$ javac -d modules --module-path modulesLibrary --module-source-path src $(find src -name “*.java” | grep -v com.apress.moduleA2)
Note

The role of grep -v or grep -invert-match is to invert the sense of matching. In our case, it excludes the files from the com.apress.moduleA2 directory and selects the ones that don’t match.

As a result, both com.apress.moduleA and com.apress.moduleB modules are compiled, and the class files are now residing in the modules directory. Next, we create modular JAR files for each of the previous compiled modules. For this, we go inside the modules directory and create two modular JAR files for each module. As input, we take the class files from the modules directory:

cd modules
$ jar --create --file=../modulesLibrary/com.apress.moduleA.jar -C com.apress.moduleA .
$ jar --create --file=../modulesLibrary/com.apress.moduleB.jar -C com.apress.moduleB .

Both modular JAR files were created in the modulesLibrary directory. Further, we attempt to compile the patch as a class. We compile the EmployeeImpl.java file from the package com.apress.moduleA located inside the directory com.apress.moduleA2:

cd ..
$ javac -Xmodule:com.apress.moduleA --module-path modules -d patchModules/com.apress.moduleA src/com.apress.moduleA2/com/apress/moduleA/EmployeeImpl.java

Here are the command-line options used:

  • -Xmodule:com.apress.moduleA specifies that the class EmployeeImpl.java should be compiled as if it’s actually part of the module com.apress.moduleA.

  • -d patchModules/com.apress.moduleA specifies the output directory where to compile the class EmployeeImpl.java.

We pass the path to the class that will be compiled, EmployeeImpl.java. As a result, the EmployeeImpl file from com.apress.moduleA2 directory has been compiled into the patchModules directory. Further, we create a JAR file for the class that we previously patched. In this case, we create a normal JAR, not a modular one. Therefore, we go inside the patchModules directory and type the following:

jar --create -file=../patchModulesLibrary/com.apress.moduleA.jar -C com.apress.moduleA .

As a result, inside the patchModulesLibrary directory a new JAR called com.apress.moduleA.jar is created.

We can run this example in two different ways: by patching module com.apress.moduleA with classes or by patching it with a JAR.

First, let’s run it by patching it with classes. In this example, we make use of the --patch-module command-line option to express that we want to patch the module com.apress.moduleA with the class existing inside the patchModules directory. To recap, inside the patchModules directory is our EmployeeImpl.class, which corresponds to the new EmployeeImpl class that we want to be used to replace the old one:

cd ..
$ java --patch-module com.apress.moduleA=patchModules/com.apress.moduleA --module-path modulesLibrary -m com.apress.moduleB/com.apress.moduleB.MainClass

We also passed the main class to the -m flag. The following output is printed at the console:

Employee first name is: Andrew
Employee last name is: Lopez
Employee department is: Big Data

As we can see in this output, the new EmployeeImpl class replaced the existing one. In the MainClass we created an object of type EmployeeImpl that represents the new EmployeeImpl class.

In the previous example, we specified the location of the .class files to the --patch-module option. As an alternative, we can also specify the location of the JAR file we previously created inside the patchModulesLibrary directory. Therefore, we run the java launcher and patch the module com.apress.moduleA with the JAR com.apress.moduleA.jar:

$ java --patch-module com.apress.moduleA=patchModulesLibrary/com.apress.moduleA.jar --module-path modulesLibrary -m com.apress.moduleB/com.apress.moduleB.MainClass

The result is the same as the one we previously had. In this example, we revealed how we can patch a module using the --patch-module command-line option first.

Now that we know how to patch a module, let’s see how we can apply this knowledge for running Junit test cases in a modular context.

Note

You can find the source code for this example in the directory /ch11/patchingAModule.

Earlier in this chapter, you saw three scenarios for unit testing in Java 9. Let’s look at practical code examples for scenario 1 (Junit test classes and types under test reside in separate modules) and scenario 2 (types under test reside in a module and the Junit test classes are on the class path).

Running a Junit Test Where the Junit Test Class and the Types Under Test Reside in Separate Modules

The following shows a very simple example of running a Junit test in Java 9. In our case, the Junit test and the classes under test reside in different modules. This scenario corresponds to scenario 1 described earlier in this chapter. We modify the previous example (the example that patched a module) in order to make it suitable for testing with Junit.

We add the following method in the Employee.java class of module com.apress.moduleA:

public String getEmployeeFullData() {
        return getFirstName() + ", " + getLastName() + ", " + getDepartment();
 }

We also need to add a Junit test case. It will reside in the module com.apress.moduleB and will simply call the method getEmployeeFullData() from the module com.apress.moduleA.

Listing 11-6 shows the class EmployeeTest.

Listing 11-6. The EmployeeTest Class
package com.apress.moduleB;

import org.junit.Assert;
import org.junit.Test;
import org.junit.Before;


import com.apress.moduleA.entity.Employee;

public class EmployeeTest {

    Employee employee;

    @Before
    public void setEmployeeData() {
        employee = new Employee();
        employee.setFirstName("Alexandru");
        employee.setLastName("Jecan");
        employee.setDepartment("IT");
    }


    @Test
    public void employeeDataTest() {
        Assert.assertEquals("Alexandru, Jecan, IT", employee.getEmployeeFullData());
    }
}

This class imports the Employee class from module com.apress.moduleA, instantiates an object of type Employee, and calls a method on it. Listing 11-7 shows the module descriptor of module com.apress.moduleB .

Listing 11-7. The module-info.java of Module com.apress.moduleB
module com.apress.moduleB {
        requires junit;
        requires com.apress.moduleA;
        exports com.apress.moduleB;
}

Module com.apress.moduleB requires module com.apress.moduleA, because it uses types from it. It also requires the module junit, because we’re using the Junit test framework. In this example, junit is an automatic module.

We define a folder called automaticModules where we put the two necessary JARs needed to run a Junit test: Junit and Hamcrest-core.

Note

The Junit and Hamcrest-core JAR files can be downloaded from the Maven repository at https://mvnrepository.com/ . You can download them using your web browser by clicking the Download (JAR) link. You don’t need to use Maven for this.

Listing 11-8 shows the content of the automaticModules folder.

Listing 11-8. The automaticModules folder
hamcrest-core-1.3jar
junit-4.12.jar

We compile both modules com.apress.moduleA and com.apress.moduleB:

javac -d modules --module-path "automaticModules;modulesLibrary" --module-source-path src $(find src -name "*.java")

The automaticModules folder, which contains both JAR files described earlier, is passed to the --module-path command-line option. In this way, the JAR files become automatic modules. Next, we switch to the modules folder and create two JARs for both modules com.apress.moduleA and com.apress.moduleB:

cd modules
jar --create --file=../modulesLibrary/com.apress.moduleA.jar -C com.apress.moduleA .
jar --create --file=../modulesLibrary/com.apress.moduleB.jar -C com.apress.moduleB .

We can now run our unit test. For this, we have to pass all the modules that we have on the module path, including the automatic modules. We can use the constant ALL-MODULE-PATH. As specified in the JDK documentation, this variable stands for all the modules on the module path. It’s much easier to specify this constant instead of specifying each module separately:

cd ..
java --module-path "automaticModules;modulesLibrary" --add-modules ALL-MODULE-PATH -m junit/org.junit.runner.JUnitCore com.apress.moduleB.EmployeeTest

The --module-path command-line option points to the automaticModules folder, which contains the Hamcrest and Junit JAR files, and to the modulesLibrary folder, which contains the com.apress.moduleA.jar and the com.apress.moduleB.jar files. The -m option gets the parameter junit/org.junit.runner.JunitCore com.apress.moduleB.EmployeeTest. In this way, we state that we want to use JunitCore from the module junit to run the tests from the class EmployeeTest, which is located inside the package com.apress.moduleB.

Instead of the constant ALL-MODULE-PATH, it would have also been possible to list the modules we need using comma. In this case, we would have had this:

--add-modules com.apress.moduleB,hamcrest.core

The result of running this test is OK , which means the test was successful and we’ve successfully managed to run the test case by accessing the other module and by reading the junit-4.12.jar and the hamcrest-core-1.3.jar files as automatic modules.

Note

You can find the source code for this example in the directory /ch11/junitSeparateModules.

Running a Junit Test Where the Junit Test Class Doesn’t Reside Inside a Module

So far, so good. In the previous example, our Junit test was in a module, and the test object was in another module. We managed to connect them and make them work. But what happens when the Junit test isn’t part of a module and instead resides on the class path? Things get a little more complicated in this case, which corresponds to scenario 2 from the beginning of the chapter.

In the next example, we have our EmployeeTest class, but this isn’t part of a module anymore. We also change the package name of the EmployeeTest class. Its new name is com.apress.moduleA. We do this because during patching, the package name has to be the same as the one from module com.apress.moduleA.

In this example, we don’t have a module com.apress.moduleB anymore, so we delete the module-info.java file from it. Listing 11-9 shows the module descriptor of module com.apress.moduleA.

Listing 11-9. The module-info.java File of Module com.apress.moduleA
module com.apress.moduleA {
    exports com.apress.moduleA.entity;
    exports com.apress.moduleA;
}

We start by compiling the module com.apress.moduleA, and afterwards we create a modular JAR out of it:

javac -d modules --module-path "automaticModules;modulesLibrary" --module-source-path src $(find src -name "*.java" | grep -v com.apress.moduleB)
cd modules
jar --create --file=../modulesLibrary/com.apress.moduleA.jar -C com.apress.moduleA .

Next, we compile the EmployeeTest.java file

cd ..
javac -d patchModules/com.apress.moduleA -Xmodule:com.apress.moduleA --add-reads com.apress.moduleA=junit --add-modules junit --module-path "modulesLibrary;automaticModules" src/com.apress.moduleB/com/apress/moduleA/EmployeeTest.java

During compilation, we used the option -Xmodule:com.apress.moduleA in order to compile the Java class EmployeeTest as if it were part of module com.apress.moduleA. The command-line option --add-reads com.apress.moduleA=junit is mandatory because module com.apress.moduleA uses Junit, so as a result a read dependency to Junit is required by the module com.apress.moduleA. The option --add-modules junit is also mandatory. We’re making use of Junit and therefore we add the automatic module junit, which is automatically created after placing the junit.jar on the module path.

After compiling the statement just mentioned, the patchModules folder will contain the EmployeeTest.class file. Further, we create a JAR containing this class file:

cd patchModules
jar --create --file=../patchModulesLibrary/com.apress.moduleA.jar -C com.apress.moduleA .

We run the Junit test by patching the module com.apress.moduleA:

cd ..
java --patch-module com.apress.moduleA=patchModules/com.apress.moduleA --module-path "automaticModules;modulesLibrary" --add-reads com.apress.moduleA=junit --add-modules ALL-MODULE-PATH -m junit/org.junit.runner.JUnitCore com.apress.moduleA.EmployeeTest

Here we patched the module using the EmployeeTest.class file located in the patchModules folder. We also added the reads dependency on Junit and added all the modules from the module path using the constant ALL-MODULE-PATH, which of course include the automatic modules. In this way, we were able to successfully run a Junit test located outside of a module that uses test objects from a module.

We could also use the JAR file com.apress.moduleA.jar for patching the module com.apress.moduleA (instead of the class files as in the previous example). This java command gives the same result as the previous example:

$ java --patch-module com.apress.moduleA=patchModulesLibrary/com.apress.moduleA.jar --module-path "automaticModules;modulesLibrary" --add-reads com.apress.moduleA=junit --add-modules ALL-MODULE-PATH -m junit/org.junit.runner.JUnitCore com.apress.moduleA.EmployeeTest
Note

You can find the source code for this example in the directory /ch11/junitTestNotInModule.

Testing with Maven

Maven and other build automation tools make our lives much easier because we don’t have to write so many command-line flags. We don’t have to write -Xmodule and --patch-module when running tests using Maven because Maven does this for us in the background.

Note

The Maven Compiler plugin starting from version 3.6.0 has Jigsaw support.

It’s much easier to compile our application and run our tests with Maven than it is by writing our entire commands with flags on the command-line. The Jigsaw support built into Maven helps us reduce the amount of work considerably.

We take our last example (from scenario 2), which has the Junit test class outside the module. We have to modify the structure of our project a little in order to make it work with Maven. Therefore, the module com.apress.moduleA has to be located under the src/main/java directory. Also, the EmployeeTest.java will be located under the src/test directory.

A pom.xml file is the essential unit of work in Maven. It contains information about the project and the configuration stuff used to build the project. Listing 11-10 shows the pom.xml file that we add in the root of our project.

Listing 11-10. The pom.xml file
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>


    <groupId>com.apress.junit</groupId>
    <artifactId>junit-testing</artifactId>
    <version>0.0.1</version>


    <properties>
        <maven.compiler.source>9</maven.compiler.source>
        <maven.compiler.target>9</maven.compiler.target>
    </properties>


    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Using the <maven.compiler.source> and <maven.compiler.target> tags, we set the source and target to 9 because we want to use JDK 9 for compiling our project. We use the Maven Compiler plugin version 3.6.1 which has Jigsaw support. We also use the Maven Dependency plugin to copy the dependencies inside the target/lib directory. We can now run mvn clean package to build the project. By building the project, our tests are also executed. We get a BUILD SUCCESS message, so the tests were successful.

In the newly created target directory, we have a classes folder that contains our class files (including the module-info.class file) that were compiled using the Maven Compiler plugin. Inside the lib folder we can find the test dependencies hamcrest-core-1.3.jar and junit-4.12.jar.

Using Maven, we didn’t have to use the -Xmodule flag or the --add-modules flag because everything was done in the background by the Maven Compiler plugin.

Note

You can find the source code for this example in the directory /ch11/junitTestNotInModuleMaven.

Summary

This chapter discussed the most important aspects of unit testing modular applications in Java 9. We learned about the -Xmodule Java compiler command-line option used to compile classes for a module. We also learned about the --patch-module command-line option used to override classes inside a module. Using these handy flags, we showed how to test an application where the types under test reside inside a module, but the Junit test classes don’t. We also looked at an example where the types under test and the Junit test classes reside in different modules. At the end of the chapter, we demonstrated how to use Maven with its Maven Compiler plugin to compile and run unit tests in JDK 9.

Chapter 12 covers the integration of Jigsaw with integrated development environments (IDEs) like Intellij IDEA and Eclipse. It also talks about how Jigsaw works together with build tools like Maven.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.206.254