In this second installment in my Maven-related articles, we will continue to look at more Maven basics with some interesting features, starting with some more dependency management and continuing with compiling the project and running our tests 💪
Maven Tutorial: Table of Contents
- Getting Started with Maven in Less Than 10 Minutes – Part 1
- You’re here → Getting Started with Maven in Less Than 10 Minutes – Part 2
Transitive dependency
Each project requires some dependencies. A dependency can be imported by any number of projects. Some of the dependencies you import into your project might, in turn, import some of the same dependencies that other dependencies you imported also import. In some cases, a dependency might import another dependency, which in turn imports others.
In this case, you do not need to import certain libraries, since they might already be imported by another one you have declared in your dependency section of the pom.xml file. But sometimes, you also need to understand who exactly brings a certain dependency into your project (out of all the dependencies you declared). To find out just who imports what, you can use a dependency Maven command. You can type the following command in the command line (if you added Maven to your computer path):
mvn dependency:tree
Otherwise, if you are using the IntelliJ Maven installation, you can open the Maven panel, then click the ‘Execute Maven Goal’ button:
In the popup that appears, you can type the same command and hit Enter:
In the IntelliJ ‘Run’ pane, the Maven command executes and then the result will be a tree structure of your project’s dependencies, similar to this:
In this example, you can see that the demo project I created imports the TestProject java-sdk artifact, which in turn imports Appium. Then, Appium imports Selenium (in this example, selenium-java). You can also see for each dependency what version is imported 💡
Dependency exclusion
In some cases, you want to import a different version of a dependency than the version of it that is imported into your project by a different dependency. Let’s say dependency A imports dependency B. In this case, you want a different version for dependency B. To achieve this, you need to exclude dependency B from being imported into the project at all. This is done by adding a <exclusion> tag in the pom.xml file to the definition of dependency A.
For example, if we want to exclude selenium-java from being imported by TestProject, we can add an exclusion tag to the TestProject definition:
<dependency> <groupId>io.testproject</groupId> <artifactId>java-sdk</artifactId> <version>1.2.0-RELEASE</version> <exclusions> <exclusion> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> </exclusion> </exclusions> </dependency>
We would want this because, let’s say, we want to try out the latest beta version of Selenium 4. In this case, apart from the exclusion we just added, we also need to add a new <dependency> tag corresponding to the latest beta version of Selenium 4 we want.
Most commonly used Maven goals
The project you created (including all the code you wrote) needs to be ‘cleaned’ and compiled from time to time. This allows you to check that the project does not throw any compilation errors due to missing code or to any incorrect configuration of the dependencies. It is recommended to perform the compilation of the project at least each time after a new dependency is added, and right before code needs to be committed into a version control repository (like Git).
Compiling the code can be done from the command line, running the following Maven command 👩💻:
mvn clean install
Similarly, you can use the Maven panel for performing this action, in IntelliJ. In the ‘Lifecycle’ section, you need to simultaneously select ‘clean’ and ‘install’:
Then, you need to inspect the ‘Run’ panel and wait for it to display ‘Build Success’. If there any compilation errors, this step will fail, and you will need to inspect the stacktrace to see what happened.
When this command runs, it will first remove any files that were generated by a previous compilation, which includes the ‘target’ folder. Then, after the compilation, it will create the project artifact and copy it to the .m2 folder on your machine. It also runs any tests you have.
But, because this is a test project, full of thousands of tests, this means the compilation would take hours or days. So you want to skip running the tests when you are compiling the project. You want to run them in other circumstances. For this, you can simply skip running the tests ✅
From the command line, this can be achieved by adding the ‘-DskipTests’ option to the clean install command:
mvn clean install -DskipTests
Similarly, from the IntelliJ Maven pane, you can also select the ‘Toggle ‘Skip Tests’ Mode’ button:
However, in case you want to only run your tests, without compiling the project also, you need to use the ‘test’ goal 🎯 A simple command using the ‘test’ goal looks like this:
mvn test
This command will run all the tests you have in the project. But of course, normally you don’t want to run everything, but instead want to be able to pick a set of tests to run.
Further details about other Maven goals, you can read this article 🧾
Using system properties with the – D option
You can, for example, select to run only those tests which have a certain group assigned to them (if you are using TestNG). Or, you can choose to run only a certain testng.xml file (which defines what tests to run).
Let’s say you have a set of tests, and some of them have the group ‘sanity‘ attached to them. In Jenkins, you have a job that only needs to run a sanity set of tests. In this case, identifying which tests are the sanity ones is easy: all the tests with the group ‘sanity’ need to run as part of this job. You will configure the Jenkins job to execute the following Maven command to achieve the desired test run:
mvn test -Dgroups=sanity
Notice that in this command we introduce the ‘-D’ option. Whenever you need to pass in a system property to your test run, this is the option you will use. The TestNG runner is aware of the ‘groups’ option and knows that if this is specified in a Maven command, it needs to look for the group name when it inspects the available tests, to identify which tests need to be run 🏃♀️
The ‘-D’ option introduces a key-value pair that represents a system property. The tests can read the system property and use it in some context. For example, you might want to run the tests on different environments, but you don’t want to create complicated logic in the test. In this case, you can pass in the environment URL as a system property, using the ‘-D’ option. The test will simply need to read this information to navigate to the desired URL. Reading the system property in a test is done easily:
System.getProperty(key)
Here, ‘key’ represents the key in the key-value pair you define next to the ‘-D’ option. For example, if you want to only run the sanity tests on the environment with url ‘http://someurl’, the Maven command you would run is:
mvn test -Dgroups=sanity -Durl=http://someurl
In this case, the test would read the ‘url’ property as follows:
System.getProperty("url")
Basically, there is a set of predefined system properties you can use, that Maven or TestNG are aware of (like ‘skipTests’, ‘groups’), or you can define your own properties. In this case, you need to add logic in the tests that reads and uses the value of the system property 😀
Using profiles
In some cases, you want to create a set of configurations that apply to the same test run, and you want to easily apply that set of configurations. For such a task, you can create a Maven profile. For example, in a profile, you could set the test’s selection to be determined by a certain testng.xml file; not to run tests with a certain group.
For such a task, in the pom.xml file, you will create a new section called <profiles> and inside it, you will define a first <profile>. Let’s name it ‘suiteExcludingGroups’. The entry you need to place in your pom.xml file, for ignoring the group named ‘sanity’ and running the suite file located at src\test\resources\feature1.xml in your project is:
<profile> <id>suiteExcludingGroups</id> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> <configuration> <excludedGroups>sanity</excludedGroups> <suiteXmlFiles> <suiteXmlFile>src\test\resources\feature1.xml</suiteXmlFile> </suiteXmlFiles> </configuration> </plugin> </plugins> </build> </profile>
In this profile, we configured the plugin which runs the TestNG tests, namely ‘maven-surefire-plugin’, to exclude a certain group, and to use a certain testng.xml file.
Running the profile can be done either from the command line or from the IntelliJ interface. For the command line, we should type the following command (using the -P option to specify which profile to use):
mvn test -PsuiteExcludingGroups
If we want to use the IntelliJ interface instead, in the Maven panel, because we now created a profile in the pom.xml file, we have a new dropdown, named Profiles. Expanding it will reveal the profiles configured in the pom.xml file. We should check the checkbox next to the profile we want to run. Then, from the Lifecycle dropdown, we should select the ‘test’ goal. To run this configuration, we now only need to click the Run button (the green triangle):
The tests which match the configuration in the profile will now be run ❗
Conclusion
In this series of 2 Maven articles, we saw how to create a brand new project, set up its required dependencies, build the project and run our tests. This helps us create a standalone project for our testing purposes, configure it for our needs, and start validating the product we work on by running our tests in different ways with different configurations 🔥