How to run JUnit 4 Test Cases in JUnit 5
By Pratik Barjatiya, Community Contributor - November 19, 2024
JUnit is a robust framework used to automate unit tests in Selenium, facilitating the testing of even the smallest individual chunk of codes.
JUnit is popular among both developers and QA as it helps detect bugs in code early on and offers features like Assertions that verify if test conditions meet expected outcomes.
JUnit 4 and JUnit 5 are different versions of the JUnit framework that is widely used by QA and development teams.
This article discusses how you can JUnit 4 test cases in the more modern version of JUnit, JUnit 5. Dive in.
What Is JUnit 5? How does it work?
JUnit 5 is the latest version of JUnit that comes with a modular structure and better flexibility. It consists of 3 special components.
Before discussing the components, it is important to note that JUnit 5 needs Java 8 or higher as the runtime environment.
Here are the three components of JUnit 5:
- JUnit Platform: This is the main engine that launches and executes tests. It enables integrations with IDEs.
- JUnit Jupiter: Offers the new programming model and extension to write tests by replacing JUnit 4’s @Test-based model.
- JUnit Vintage: Enables compatibility by allowing JUnit 3 and JUnit 4 tests to be run on JUnit 5.
Working
JUnit 5 defines tests with annotations like @Test, @BeforeEach, @AfterEach, etc., in JUnit Jupiter, which in turn defines test setup, execution, and teardown. The JUnit platform then manages test execution, integration with tools(IDEs, Gradle etc.), and report of results.
JUnit 5 comes with a modular design that enables the use of advanced features like parameterized tests, nested test classes, and customized test extensions etc. that enhances the flexibility and efficiency of testing framework.
JUnit 5 vs JUnit 4
Here are the core differences between JUnit 5 and JUnit 4:
JUnit 5 | JUnit 4 |
---|---|
JUnit 5 consists of 3 additives specifically JUnit Platform, JUnit Jupiter, and JUnit Vintage | In JUnit 4, everything is wrapped together in a monolithic structure |
JUnit 5 calls for Java 8 (or maybe better) | JUnit 4 calls for a Java 5 (or above) |
In JUnit 5, the JUnit Platform provides aid for build tools and integrated development platforms and famous IDEs like Eclipse, Visual Studio, and IntelliJ | JUnit 4 doesn`t aid any third-celebration integration plugins and IDEs |
In JUnit 5, assertions techniques are grouped and can be imported from org.junit.jupiter.Assertions | . In JUnit 4, assertions (or asserts) are grouped below org.junit.Assert package deal which includes all of the announcement techniques |
in JUnit 5, assumptions are imported from org.junit.jupiter.api.Assumptions | In JUnit 4, assumption techniques are imported from org.junit.Assume |
When returning errors/blunders messages in assertions, the order of the parameters differs for JUnit 5 as given below public static void assertEquals(long expected, long actual, String message) | When returning errors/blunders messages in assertions, the order of the parameters differs for JUnit 4 as given below public static void assertEquals(String message, long expected, long actual) |
In JUnit 4,
public static void assertEquals(String message, long expected, long actual)
In JUnit 5,
public static void assertEquals(long expected, long actual, String message)
@Rule and @ClassRule annotations in JUnit 4 are removed. Instead, @ExtendWith and @RegisterExtension should be used.
JUnit 4 Operation of @Test annotation @Test(expected = Exception.class) public void testThrowsException() throws Exception { } JUnit 5 Operation of @Test annotation @Test void testThrowsException() throws Exception { Assertions.assertThrows(Exception.class, () -> { }); }
Annotations In JUnit 4 And JUnit 5
Annotations are vital in JUnit, which are listed as follows:
- @Test – Annotation used to define and declare a test.
- @RepeatedTest – Annotation used to specify that the function is a template for the tests that can be repeated a specific number of times.
- @TestFactory– Annotation used for defining a system which is a test plant for robust tests that are generated at runtime.
- @TestMethodOrder – Annotation used to define the order of carrying out the tests.
- @DisplayName – Annotation used to specify a custom display name for the function or class.
- @Tag – Annotation used to filter the tests at function or class position by defining the markers.
- @Disabled – Annotation used to disable a test system or class.
- @ParameterizedTest – Annotation used to indicate that the function is a parameterized test. These parameterized tests are analogous to normal test styles but you have to specify a source to give parameters for each incantation which in turn is used in the test.
- @BeforeEach – Annotation used to specify that the specific test system has to be executed before each@Test, @RepeatedTest, @ParameterizedTest, or @TestFactory system.
- @AfterEach – Annotation used to specify that the specific test system has to be executed after each@Test, @RepeatedTest, @ParameterizedTest, or @TestFactory system.
- @BeforeAll – Annotation used to specify that the specific test system has to be executed before all @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory system.
- @AfterAll – Annotation used to specify that the specific test system has to be executed after all @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory system.
Differences in Annotations in JUnit 4 And JUnit 5
Here are some of the annotations that vary in JUnit 4 and JUnit 5:
JUNIT 4 | JUNIT 5 |
---|---|
@Before | @BeforeEach |
@After | @AfterEach |
@BeforeClass | @BeforeAll |
@AfterClass | @AfterAll |
@Ignore | @Disabled |
@Category | @Tag |
Step-by-Step Execution Flow Of Annotations In JUnit 5
Let us see the series wherein those annotations are carried out in JUnit 5.
- The technique applied below the @BeforeAll annotation is carried out.
- The technique applied below the @BeforeEach annotation executes earlier than the primary take a look at.
- The technique applied below the @Test annotation is carried out.
- The technique applied below the @AfterEach annotation runs after the test is carried out.
- The technique applied below the @AfterAll annotation might be carried out on the give up.
In Selenium Automation Testing with the JUnit, you would possibly need to put in force more than one take a look at scenarios (or techniques) below the equal class (or special classes).
As visible in the sooner part of the JUnit 5 tutorial, it’s vital to understand the execution order of the techniques applied below special annotations.
Here is the order of execution in case there are multiple tests to take a look at techniques withinside the class:
- The technique applied below the @BeforeAll annotation is carried out as soon as.
- The technique applied below the @BeforeEach annotation executes earlier than the primary take a look at.
- The technique applied below the @Test annotation is carried out.
- The technique applied below the @AfterEach annotation runs after the take a look at the case is carried out.
- The technique applied below the @BeforeEach annotation is carried out earlier than the second test case.
- The technique applied below the @Test annotation is carried out.
- The technique applied below the @AfterEach annotation is carried out after the execution of the second test case.
- The technique that has been annotated with @AfterAll is carried out as soon as on the give up.
Thus, for more than one take a look at instances, the techniques applied below @BeforeAll and @AfterAll annotations are carried out most effective as soon as, at the start and give up of the take a look at.
Alternatively, take a look at the techniques applied below the @BeforeEach and @AfterEach annotations are carried out before and after respectively, for every and each take a look at case.
Read More: How to Ignore a Base Test Class in JUnit
Migrating Tests from JUnit 4 to JUnit 5
One of the not unusual place questions that involves JUnit customers is `Is it possible to run JUnit 4 assessments in JUnit 5`? By giving up on this phase of JUnit 5 tutorial, you’ll be in a role to run JUnit 4 assessments in JUnit 5. You can migrate assessments written in JUnit 4 to JUnit 5 with minimum effort.
Here are the steps to carry out the migration:
- The JUnit Vintage engine in JUnit 5 facilitates strolling, take a look at instances written in JUnit 4 (or JUnit 3) utilising the JUnit Platform.
- Annotations like @Before and @After in present JUnit 4 assessments should get replaced with equal annotations in JUnit 5 (i.e. @BeforeEach and @AfterEach respectively).
- Import accurate package deal for Assertions (i.e. org.junit.jupiter.Assertions) and Assumptions
- Make absolute the JUnit 4 annotations that are unsupported in JUnit 5 and replace those annotations with the ones supported in JUnit 5.
- In case your JUnit 4 design is defined with certain rules, the migration has to be done gradually. There are many rules of JUnit 4 that are compatible with JUnit Jupiter.
- The junit-jupiter-migration support module provides support for the @Ignore reflection in JUnit 4 which is original to Jupiter’s @Disabled reflection. Still, this is still in the experimental phase
For instance, let’s perceive how to run the test project that contains unit tests fabricated utilising JUnit 4 and JUnit 5.
Assuming having tests composed utilising JUnit 4. Adding the JUnit 5 tests to it. The pom.xml record would look like below:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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>RunJUnitTest</groupId> <artifactId>RunJUnitTest</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.28</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <version>1.2.0</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.2.0</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.28</version> <scope>test</scope> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.0.0-alpha-7</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-remote-driver</artifactId> <version>4.0.0-alpha-7</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-chrome-driver</artifactId> <version>4.0.0-alpha-7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> </dependencies> <properties> <maven.compiler.source>15</maven.compiler.source> <maven.compiler.target>15</maven.compiler.target> </properties> </project>
JUnit 4 Test
package com.browserstack; import com.browserstack.local.Local; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPut; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.junit.After; import org.junit.Before; import org.junit.runner.RunWith; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import org.openqa.selenium.WebDriver; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.SessionId; import java.io.FileReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.*; @RunWith(Parallelized.class) public class BrowserStackJUnitTest { public static String username, accessKey; private static JSONObject config; public WebDriver driver; @Parameter(value = 0) public int taskID; private Local l; @Parameters public static Iterable<? extends Object> data() throws Exception { List<Integer> taskIDs = new ArrayList<Integer>(); if (System.getProperty("config") != null) { JSONParser parser = new JSONParser(); config = (JSONObject) parser.parse(new FileReader("src/test/resources/conf/" + System.getProperty("config"))); int envs = ((JSONArray) config.get("environments")).size(); for (int i = 0; i < envs; i++) { taskIDs.add(i); } } return taskIDs; } public static void mark(SessionId sessionID, String status, String reason) throws URISyntaxException, IOException { URI uri = new URI("https://" + username + ":" + accessKey + "@api.browserstack.com/automate/sessions/" + sessionID + ".json"); HttpPut putRequest = new HttpPut(uri); ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(); nameValuePairs.add((new BasicNameValuePair("status", status))); nameValuePairs.add((new BasicNameValuePair("reason", reason))); putRequest.setEntity(new UrlEncodedFormEntity(nameValuePairs)); HttpClientBuilder.create().build().execute(putRequest); } @Before public void setUp() throws Exception { JSONArray envs = (JSONArray) config.get("environments"); DesiredCapabilities capabilities = new DesiredCapabilities(); Map<String, String> envCapabilities = (Map<String, String>) envs.get(taskID); Iterator it = envCapabilities.entrySet().iterator(); while (it.hasNext()) { Map.Entry pair = (Map.Entry) it.next(); capabilities.setCapability(pair.getKey().toString(), pair.getValue().toString()); } Map<String, String> commonCapabilities = (Map<String, String>) config.get("capabilities"); it = commonCapabilities.entrySet().iterator(); while (it.hasNext()) { Map.Entry pair = (Map.Entry) it.next(); if (capabilities.getCapability(pair.getKey().toString()) == null) { capabilities.setCapability(pair.getKey().toString(), pair.getValue().toString()); } } username = System.getenv("BROWSERSTACK_USERNAME"); if (username == null) { username = (String) config.get("user"); } accessKey = System.getenv("BROWSERSTACK_ACCESS_KEY"); if (accessKey == null) { accessKey = (String) config.get("key"); } if (capabilities.getCapability("browserstack.local") != null && capabilities.getCapability("browserstack.local") == "true") { l = new Local(); Map<String, String> options = new HashMap<String, String>(); options.put("key", accessKey); l.start(options); } driver = new RemoteWebDriver(new URL("https://" + username + ":" + accessKey + "@" + config.get("server") + "/wd/hub"), capabilities); } @After public void tearDown() throws Exception { driver.quit(); if (l != null) l.stop(); } }
JUnit 5 Test
package tests; //* import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import runners.WebDriverTest; import utils.MarkSessionStatus; public class SingleTest { @WebDriverTest void singleTest(WebDriver driver) { SessionId sessionId = ((RemoteWebDriver)driver).getSessionId(); MarkSessionStatus sessionStatus = new MarkSessionStatus(sessionId); try { driver.get("https://bstackdemo.com/"); final WebDriverWait wait = new WebDriverWait(driver, 10); wait.until(ExpectedConditions.titleIs("StackDemo")); String product_name = wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[@id='1']/p"))).getText(); WebElement cart_btn = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//*[@id='1']/div[4]"))); cart_btn.click(); wait.until(ExpectedConditions.visibilityOfElementLocated(By.className("float-cart__content"))); final String product_in_cart = wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[@id='__next']/div/div/div[2]/div[2]/div[2]/div/div[3]/p[1]"))).getText(); if (product_name.equals(product_in_cart)) { sessionStatus.markTestStatus("passed", "Product has been successfully added to the cart!"); } else { sessionStatus.markTestStatus("failed", "There was some issue!"); } } catch (Exception e) { sessionStatus.markTestStatus("failed", "There was some issue!"); System.out.println("Exception: " + e.getMessage()); } driver.quit(); } //@WebDriverTest void bstackTest(WebDriver driver) { driver.get("https://bstackdemo.com/"); System.out.println("Test 1: " + Thread.currentThread().getName()); driver.quit(); } }
Parallel Tests in JUnit 5
package runners; //* import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.junit.jupiter.api.extension.*; import org.openqa.selenium.WebDriver; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import utils.SetupLocalTesting; import java.io.FileReader; import java.net.MalformedURLException; import java.net.URL; import java.util.*; import java.util.stream.Stream; public class BstackRunner implements TestTemplateInvocationContextProvider { public WebDriver driver; public DesiredCapabilities capabilities; public String username, accesskey, server; private JSONObject mainConfig; private JSONObject browserConfig; private JSONObject profileConfig; private JSONObject testConfig; private JSONObject platformConfig; private JSONObject commonCapsConfig; private HashMap<String, String> allCapsMap; private HashMap<String, String> commonCapsMap; public BstackRunner() { this.username = setupCredsAndServer().get("username"); this.accesskey = setupCredsAndServer().get("accesskey"); this.server = setupCredsAndServer().get("server"); } public HashMap<String, String> setupCredsAndServer() { try { JSONParser parse = new JSONParser(); mainConfig = (JSONObject) parse.parse(new FileReader("src/test/resources/caps.json")); server = (String) mainConfig.get("server"); username = System.getenv("BROWSERSTACK_USERNAME"); if (username == null) { username = (String) mainConfig.get("user"); } accesskey = System.getenv("BROWSERSTACK_ACCESS_KEY"); if (accesskey == null) { accesskey = (String) mainConfig.get("key"); } } catch (Exception e) { System.out.println(e.getMessage()); } HashMap<String, String> creds = new HashMap(); creds.put("username", username); creds.put("accesskey", accesskey); creds.put("server", server); return creds; } @Override public boolean supportsTestTemplate(ExtensionContext extensionContext) { return true; } @Override public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext extensionContext) { List<TestTemplateInvocationContext> desiredCapsInvocationContexts = new ArrayList<>(); //picks the test profile based on the maven command executed - single, local, parallel String profile = System.getProperty("config"); try { testConfig = (JSONObject) mainConfig.get("tests"); profileConfig = (JSONObject) testConfig.get(profile); platformConfig = (JSONObject) profileConfig.get("platform"); commonCapsConfig = (JSONObject) profileConfig.get("common_caps"); commonCapsMap = (HashMap<String, String>) commonCapsConfig; Iterator platformIterator = platformConfig.keySet().iterator(); while (platformIterator.hasNext()) { capabilities = new DesiredCapabilities(); Iterator commonCapsIterator = commonCapsMap.entrySet().iterator(); while (commonCapsIterator.hasNext()) { Map.Entry capsName = (Map.Entry) commonCapsIterator.next(); capabilities.setCapability((String) capsName.getKey(), capsName.getValue()); } final String platformName = (String) platformIterator.next(); browserConfig = (JSONObject) platformConfig.get(platformName); allCapsMap = (HashMap<String, String>) browserConfig; Iterator finalCapsIterator = allCapsMap.entrySet().iterator(); while (finalCapsIterator.hasNext()) { Map.Entry pair = (Map.Entry) finalCapsIterator.next(); capabilities.setCapability((String) pair.getKey(), pair.getValue()); } //Initialising local testing connection if (capabilities.getCapability("browserstack.local") != null && capabilities.getCapability("browserstack.local").toString().equals("true")) { HashMap<String, String> localOptions = new HashMap<>(); localOptions.put("key", accesskey); //Add more local options here, e.g. forceLocal, localIdentifier, etc. SetupLocalTesting.createInstance(localOptions); } desiredCapsInvocationContexts.add(invocationContext(capabilities)); } } catch (Exception e) { System.out.println(e); } return desiredCapsInvocationContexts.stream(); } private TestTemplateInvocationContext invocationContext(DesiredCapabilities caps) { return new TestTemplateInvocationContext() { @Override public List<Extension> getAdditionalExtensions() { return Collections.singletonList(new ParameterResolver() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType().equals(WebDriver.class); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { try { driver = new RemoteWebDriver(new URL("https://" + username + ":" + accesskey + "@" + server + "/wd/hub"), caps); } catch (MalformedURLException e) { e.printStackTrace(); } return driver; } }); } }; } }
Refer to the BrowserStack’s JUnit Repository on GitHub to learn more about executing JUnit 5 Tests on real devices
Advantages Of Using JUnit 5 For Unit Testing
Some of the core benefits of JUnit 5 are:
- Offers specific functions to outline the assessments in evaluation to the preceding versions.
- Allows using lambda features withinside the assessments for assertions in addition to assumptions.
- Offers distinguished annotations with well-described functions that may be used to beautify the unit assessments.
- Allows use of more than one runner.
- It has a specific extensible architecture.
- Migrates current tests written with preceding JUnit versions.
Conclusion
JUnit testing is the most preferred testing method if the project has been developed in Java. It is powerful and continually evolving for better test case execution. It has become a preferred choice for Test-driven development cycle.
Selenium is a convenient tool when it comes to automated web testing and using it along with JUnit is even more beneficial. JUnit supports multiple assertions and annotations. Using BrowserStack Automate, developers can conveniently run unit testing using Java with Selenium.
However, tests, no matter how well crafted, cannot be regarded as conclusive if they are not run on real devices. Emulators and simulators cannot replicate real user conditions properly, especially when it comes to aspects like weak networks and screen orientation. To compensate for these inadequacies, run tests directly on a real device cloud.
Use parallel testing. Instead of running tests sequentially, parallel testing allows for simultaneous test execution. By running JUnit Tests on cloud Selenium Grid you can run your Unit tests on multiple real browser-device combinations simultaneously, significantly reducing time and effort.
Run JUnit Tests on Real Device Cloud