DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Why Testing is a Long-Term Investment for Software Engineers
  • JUnit, 4, 5, Jupiter, Vintage
  • Readability in the Test: Exploring the JUnitParams
  • Creating Your Swiss Army Knife on Java Test Stack

Trending

  • Proactive Security in Distributed Systems: A Developer’s Approach
  • Tired of Spring Overhead? Try Dropwizard for Your Next Java Microservice
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  • Using Python Libraries in Java
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. JUnit 5 Dynamic Tests — Generate Tests at Runtime

JUnit 5 Dynamic Tests — Generate Tests at Runtime

Knowing the difference between static and dynamic tests, and how to generate them at run-time.

By 
Michael Pham user avatar
Michael Pham
·
Aug. 25, 16 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
33.7K Views

Join the DZone community and get the full member experience.

Join For Free

In this article, I’d like to introduce about JUnit 5 Dynamic Tests feature which allows us to declare and run test cases generated at runtime.

1. Static Tests vs. Dynamic Tests

1.1. Static Tests

To get to know about the Dynamic Tests vs. Static Tests, let take a look at an example below. We have a very simple TranslatorEngine class which is responsible for translating text from English to French. Note that the class is implemented basically, without optimization, to make it easy to understand.

public class TranslatorEngine {

  public String tranlate(String text) {
    if (StringUtils.isBlank(text)) {
      throw new IllegalArgumentException("Translated text must not be empty.");
    }
    if ("Hello".equalsIgnoreCase(text)) {
      return "Bonjour";

    } else if ("Yes".equalsIgnoreCase(text)) {
      return "Oui";

    } else if ("No".equalsIgnoreCase(text)) {
      return "Non";

    } else if ("Goodbye".equalsIgnoreCase(text)) {
      return "Au revoir";

    } else if ("Good night".equalsIgnoreCase(text)) {
      return "Bonne nuit";

    } else if ("Thank you".equalsIgnoreCase(text)) {
      return "Merci";
    } else {
      return "Not found";
    }
  }

}

Now, let’s write some tests for this class. Basically, we can come up with several test cases as follows. Note that these test cases are still not enough for the translate method yet.

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
public class TranslationEngineTest {

  private TranslatorEngine translatorEngine;

  @BeforeEach
  public void setUp() {
    translatorEngine = new TranslatorEngine();
  }

  @Test
  public void testTranlsateHello() {
    assertEquals("Bonjour", translatorEngine.tranlate("Hello"));
  }

  @Test
  public void testTranlsateYes() {
    assertEquals("Oui", translatorEngine.tranlate("Yes"));
  }

  @Test
  public void testTranlsateNo() {
    assertEquals("Non", translatorEngine.tranlate("No"));
  }
}

These above tests are called Static Tests. They are are static in the sense that they are fully specified at compile time, and their behavior cannot be changed at run-time. Note that we have just written 3 test cases while the current translate method can support translate 6 words or phrases. We may need to add three more static tests for three remaining words or phrases and other cases like Null or empty word or phrases, not supported words or phrases, etc.

When we run the test, basically, it will look for all the tests which were defined by annotating the @Test annotation and run them.

If the translate method supports for more words, phrases, or sentences, say 1000, we may need to add 1000 more test cases tediously for this method.

1.2. Dynamic Tests

Contrary to the Static Tests, which allow us to define a static number of fixed test cases at compile time, Dynamic Tests allow us to define the test case dynamically at runtime.

Let’s see an example about Dynamic Tests:

public void translateDynamicTests() {

    List<String> inPhrases = new ArrayList<>(Arrays.asList("Hello", "Yes", "No"));
    List<String> outPhrases = new ArrayList<>(Arrays.asList("Bonjour", "Oui", "Non"));
    Collection<DynamicTest> dynamicTests = new ArrayList<>();

    for (int i = 0; i < inPhrases.size(); i++) {

      String phr = inPhrases.get(i);
      String outPhr = outPhrases.get(i);

      // create an test execution
      Executable exec = () -> assertEquals(outPhr, translatorEngine.tranlate(phr));

      // create a test display name
      String testName = " Test tranlate " + phr;
      // create dynamic test
      DynamicTest dTest = DynamicTest.dynamicTest(testName, exec);

      // add the dynamic test to collection
      dynamicTests.add(dTest);
    }
  }

We have tried to create a collection of test cases by iterating through the list of data. Those tests are dynamic in the sense that they are generated at runtime, and the number of test cases are dependant on the data: the number of the input words or phrases.

That was an simple example of dynamic tests. Let’s see in detail how we can fully create dynamic tests by using JUnit 5.

2. JUnit 5 Dynamic Tests

In JUnit 5, dynamic test cases are represented by DynamicTest class. Here are some essential points:

  • Dynamic tests can be generated by a factory method annotated with @TestFactory, which is a new annotation of JUnit 5.

  • @TestFactory method must return a Stream, Collection, Iterable, or Iterator of DynamicTest instances.

  • @TestFactory methods must not be private or static, and may optionally declare parameters to be resolved by ParameterResolvers.

2.1. Dynamic Tests Examples

All the example source code can be found on Github. You need to get JUnit 5 be ready for your environment. You can do this by yourselves or refer to this guideline to get JUnit 5 setup with Eclipse, Maven, and Gradle.

We will modify the above test method to make it comply with the syntax of JUnit 5.

 @TestFactory
  public Collection<DynamicTest> translateDynamicTests() {

    List<String> inPhrases =
        new ArrayList<>(Arrays.asList("Hello", "Yes", "No", "Goodbye", "Good night", "Thank you"));
    List<String> outPhrases =
        new ArrayList<>(Arrays.asList("Bonjour", "Oui", "Non", "Au revoir", "Bonne nuit", "Merci"));

    Collection<DynamicTest> dynamicTests = new ArrayList<>();

    for (int i = 0; i < inPhrases.size(); i++) {

      String phr = inPhrases.get(i);
      String outPhr = outPhrases.get(i);

      // create an test execution
      Executable exec = () -> assertEquals(outPhr, translatorEngine.tranlate(phr));

      // create a test display name
      String testName = "Test tranlate " + phr;
      // create dynamic test
      DynamicTest dTest = DynamicTest.dynamicTest(testName, exec);

      // add the dynamic test to collection
      dynamicTests.add(dTest);
    }
    return dynamicTests;
  }

We have annotated the method with the @TestFactory annotation and changed the return type to Collection<DynamicTest>.

This example is very straightforward and easy to understand. Let’s tune it to be conformed with Java 8 style and return a Stream of DynamicTest instead of collection.

@TestFactory
  public Stream<DynamicTest> translateDynamicTestsFromStream() {

    List<String> inPhrases =
        new ArrayList<>(Arrays.asList("Hello", "Yes", "No", "Goodbye", "Good night", "Thank you"));
    List<String> outPhrases =
        new ArrayList<>(Arrays.asList("Bonjour", "Oui", "Non", "Au revoir", "Bonne nuit", "Merci"));

    return inPhrases.stream().map(phrs -> DynamicTest.dynamicTest("Test translate " + phrs, () -> {
      int idx = inPhrases.indexOf(phrs);
      assertEquals(outPhrases.get(idx), translatorEngine.tranlate(phrs));
    }));
  }

As mentioned above, the factory method must return a Stream, Collection, Iterable, or Iterator. We will try return an Iterable of DynamicTest instances.

TestFactory
  public Iterable<DynamicTest> translateDynamicTestsFromIterate() {

    List<String> inPhrases =
        new ArrayList<>(Arrays.asList("Hello", "Yes", "No", "Goodbye", "Good night", "Thank you"));
    List<String> outPhrases =
        new ArrayList<>(Arrays.asList("Bonjour", "Oui", "Non", "Au revoir", "Bonne nuit", "Merci"));

    return inPhrases.stream().map(phrs -> DynamicTest.dynamicTest("Test translate " + phrs, () -> {
      int idx = inPhrases.indexOf(phrs);
      assertEquals(outPhrases.get(idx), translatorEngine.tranlate(phrs));
    })).collect(Collectors.toList());
  }

And finally, we will try to return an Iterator of DynamicTest instances.


  @TestFactory
  public Iterator<DynamicTest> translateDynamicTestsFromIterator() {

    List<String> inPhrases =
        new ArrayList<>(Arrays.asList("Hello", "Yes", "No", "Goodbye", "Good night", "Thank you"));
    List<String> outPhrases =
        new ArrayList<>(Arrays.asList("Bonjour", "Oui", "Non", "Au revoir", "Bonne nuit", "Merci"));

    return inPhrases.stream().map(phrs -> DynamicTest.dynamicTest("Test translate " + phrs, () -> {
      int idx = inPhrases.indexOf(phrs);
      assertEquals(outPhrases.get(idx), translatorEngine.tranlate(phrs));
    })).iterator();
  }

2.2. Running Tests

To run the test on Eclipse, simply Right Click –> Run As –> JUnit Tests.

As for running with Maven and Gradle, you can refer to this post: JUnit 5 Basic Introduction.

Here is the output in Eclipse:

Test results on Eclipse

3. Summary

We have gotten to know about JUnit 5's Dynamic Test feature, which allow us to create test cases at runtime. In my opinion, dynamic testing is necessary to help reduce the effort in writing tests. However, Dynamic Tests is still an experimental feature in the current version of JUnit 5, 5.0.0-M2. This feature may be removed without prior notice. Besides, the execution lifecycle of a dynamic test is quite different with a standard @Test case. One essential point should be noted that there are not any lifecycle callbacks for dynamic tests. This means that @BeforeEach and @AfterEach methods and their corresponding extension callbacks are not executed for dynamic tests.

In future posts, I’d like to continue deep dive into other features of JUnit 5. Recently, I have some posts related to JUnit 5 tutorial. If you’re interesting in them, you can find them here.

Testing JUnit

Opinions expressed by DZone contributors are their own.

Related

  • Why Testing is a Long-Term Investment for Software Engineers
  • JUnit, 4, 5, Jupiter, Vintage
  • Readability in the Test: Exploring the JUnitParams
  • Creating Your Swiss Army Knife on Java Test Stack

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: