https://github.com/openliberty/guide-microshed-testing
A guide on how to test a MicroProfile or Jakarta EE application using MicroShed Testing.
https://github.com/openliberty/guide-microshed-testing
Last synced: about 1 year ago
JSON representation
A guide on how to test a MicroProfile or Jakarta EE application using MicroShed Testing.
- Host: GitHub
- URL: https://github.com/openliberty/guide-microshed-testing
- Owner: OpenLiberty
- License: other
- Created: 2019-10-17T14:00:45.000Z (over 6 years ago)
- Default Branch: prod
- Last Pushed: 2024-09-12T15:09:55.000Z (over 1 year ago)
- Last Synced: 2025-03-22T21:51:09.300Z (about 1 year ago)
- Language: Java
- Homepage: https://openliberty.io/guides/microshed-testing.html
- Size: 194 KB
- Stars: 3
- Watchers: 5
- Forks: 5
- Open Issues: 2
-
Metadata Files:
- Readme: README.adoc
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
// Copyright (c) 2019, 2023 IBM Corporation and others.
// Licensed under Creative Commons Attribution-NoDerivatives
// 4.0 International (CC BY-ND 4.0)
// https://creativecommons.org/licenses/by-nd/4.0/
//
// Contributors:
// IBM Corporation
//
:projectid: microshed-testing
:page-layout: guide-multipane
:page-duration: 20 minutes
:page-releasedate: 2019-11-04
:page-guide-category: microprofile
:page-essential: true
:page-essential-order: 5
:page-description: Learn how to use MicroShed Testing to test a MicroProfile or Jakarta EE application.
:page-seo-title: Testing a MicroProfile or Jakarta EE application using MicroShed Testing with an Open Liberty docker container
:page-seo-description: A tutorial on how to develop tests for a MicroProfile microservice or a Jakarta EE application by using Open Liberty dev mode provided by the Liberty Maven plugin.
:guide-author: Open Liberty
:page-tags: ['MicroProfile', 'Jakarta EE', 'Docker']
:page-related-guides: ['rest-intro', 'docker', 'rest-client-java']
:page-permalink: /guides/{projectid}
:repo-description: Visit the https://openliberty.io/guides/{projectid}.html[website] for the rendered version of the guide.
:common-includes: https://raw.githubusercontent.com/OpenLiberty/guides-common/prod
= Testing a MicroProfile or Jakarta EE application
[.hidden]
NOTE: This repository contains the guide documentation source. To view the guide in published form, view it on the https://openliberty.io/guides/{projectid}.html[Open Liberty website].
Learn how to use MicroShed Testing to test a MicroProfile or Jakarta EE application.
== What you'll learn
You'll start with an existing REST application that runs on Open Liberty and use https://microshed.org/microshed-testing/[MicroShed Testing^] to write tests for the application that exercise the application in a Docker container.
Sometimes tests might pass in development and testing (dev/test) environments, but fail in production because the application runs differently in production than in dev/test. Fortunately, you can minimize these differences between dev/test and production by testing your application in the same Docker container that you'll use in production.
=== What is Docker?
Docker is a tool that you can use to deploy and run applications with containers. You can think of Docker as a virtual machine that runs various applications. However, unlike with a typical virtual machine, you can run these applications simultaneously on a single system and independent of one another.
Learn more about Docker on the https://www.docker.com/what-docker[official Docker website^].
== Additional prerequisites
Before you begin, Docker needs to be installed. For installation instructions, refer to the https://docs.docker.com/get-docker/[official Docker documentation^]. You'll test the application in Docker containers.
Make sure to start your Docker daemon before you proceed.
[role="command"]
include::{common-includes}/gitclone.adoc[]
=== Try what you'll build
The `finish` directory in the root of this guide contains the finished application. Give it a try before you proceed.
First, review the [hotspot file=0]`PersonServiceIT` class to see what the tests look like:
// cloud hosted instruction
ifdef::cloud-hosted[]
From the menu of the IDE, select ***File*** > ***Open*** > guide-microshed-testing/finish/src/test/java/io/openliberty/guides/testing/PersonServiceIT.java
endif::[]
PersonServiceIT.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/test/java/io/openliberty/guides/testing/PersonServiceIT.java[]
----
To try out the application, go to the `finish` directory and run the following Maven goal to build the application and run the integration tests on an Open Liberty server in a container:
// static guide instructions:
ifndef::cloud-hosted[]
[role='command']
```
cd finish
mvn verify
```
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
```bash
cd /home/project/guide-microshed-testing/finish
export TESTCONTAINERS_RYUK_DISABLED=true
mvn verify
```
endif::[]
This command might take some time to run initially because the dependencies and the Docker image for Open Liberty must download. If you run the same command again, it will be faster.
The previous example shows how you can run integration tests from a cold start. With Open Liberty dev mode, you can use MicroShed Testing to run tests on an active Open Liberty server. Run the following Maven goal to start Open Liberty in dev mode:
[role='command']
```
mvn liberty:dev
```
After you see the following message, your application server in dev mode is ready:
[role="no_copy"]
----
**************************************************************
* Liberty is running in dev mode.
----
After the Open Liberty server starts and you see the `To run tests on demand, press Enter.` message, you can press the `enter/return` key to run the integration tests. After the tests finish, you can press the `enter/return` key to run the tests again, or you can make code changes to the application or tests. Dev mode automatically recompiles and updates any application or test code changes that you make.
After you're finished running tests, exit dev mode by pressing `CTRL+C` in the command-line session where you ran the server.
== Bootstrapping your application for testing
// static guide instructions:
ifndef::cloud-hosted[]
Navigate to the `start` directory to begin.
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
To begin, run the following command to navigate to the ***start*** directory:
```bash
cd /home/project/guide-microshed-testing/start
```
endif::[]
[role=command]
include::{common-includes}/devmode-lmp33-start.adoc[]
Wait for the `To run tests on demand, press Enter.` message, and then press the `enter/return` key to run the tests. You see that one test runs:
[role="no_copy"]
----
Running integration tests...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running io.openliberty.guides.testing.PersonServiceIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s - in io.openliberty.guides.testing.PersonServiceIT
Results:
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Integration tests finished.
----
To begin bootstrapping, import the [hotspot=importMicroShedTest file=0]`MicroShedTest` annotation and annotate the `PersonServiceIT` class with [hotspot=microShedTest file=0]`@MicroShedTest`. This annotation indicates that the test class uses MicroShed Testing.
The `PersonServiceIT` class outlines some basic information that informs how MicroShed Testing starts the application runtime and at which URL path the application is available:
[role="code_command hotspot file=0", subs="quotes"]
----
#Replace the `PersonServiceIT` class.#
`src/test/java/io/openliberty/guides/testing/PersonServiceIT.java`
----
PersonServiceIT.java
[source, java, linenums, role='code_column hide_tags=copyright,importAssertNotNull,importSharedContainerConfig,sharedContainerConfig,testCreatePerson']
----
include::hotspots/src/test/java/io/openliberty/guides/testing/PersonServiceIT.1.java[]
----
Import the [hotspot=importMPApp file=0]`ApplicationContainer` class and the [hotspot=importContainer file=0]`Container` annotation, create the [hotspot=mpApp file=0]`ApplicationContainer` application, and annotate the application with [hotspot=container file=0]`@Container` annotation.
The [hotspot=withAppContextRoot file=0]`withAppContextRoot(String)` method indicates the base path of the application. The app context root is the portion of the URL after the hostname and port. In this case, the application is deployed at the `\http://localhost:9080/guide-microshed-testing` URL, so the app context root is [hotspot=withAppContextRoot file=0]`/guide-microshed-testing`.
server.xml
[source, xml, linenums, role='code_column']
----
include::finish/src/main/liberty/config/server.xml[]
----
The [hotspot=withReadinessPath file=0]`withReadinessPath(String)` method indicates what path is polled by HTTP to determine application readiness. MicroShed Testing automatically starts the ApplicationContainer application and waits for it to be ready before the tests start running. In this case, you're using the default application readiness check at the http://localhost:9080/health/ready[http://localhost:9080/health/ready^] URL, which is enabled by the [hotspot=mpHealth file=1]`MicroProfile Health` feature in the server.xml configuration file. When the readiness URL returns the `HTTP 200` message, the application is considered ready and the tests begin running.
Save your changes to the `PersonServiceIT` class and press the `enter/return` key in your console window to rerun the tests. You still see only one test running, but the output is different. Notice that MicroShed Testing is using a `hollow` configuration mode. This configuration mode means that MicroShed Testing is reusing an existing application runtime for the test, not starting up a new application instance each time you initiate a test run.
== Talking to your application with a REST client
With MicroShed Testing, applications are exercised in a black-box fashion. Black-box means the tests can't access the application internals. Instead, the application is exercised from the outside, usually with HTTP requests. To simplify the HTTP interactions, a REST client is injected into the tests. To do this, you imported the [hotspot=importInject file=0]`org.microshed.testing.jaxrs.RESTClient` annotation, created a [hotspot=personSvc file=0]`PersonService` REST client, and annotated the REST client with [hotspot=inject file=0]`@RESTClient`.
In this example, the [hotspot=personSvc file=0]`PersonService` injected type is the same [hotspot file=1]`io.openliberty.guides.testing.PersonService` class that is used in your application. However, the _instance_ that gets injected is a REST client proxy. So, if you call `personSvc.createPerson("Bob", 42)`, the REST client makes an HTTP POST request to the application that is running at `\http://localhost:9080/guide-microshed-testing/people` URL, which triggers the corresponding Java method in the application.
PersonServiceIT.java
[source, java, linenums, role='code_column hide_tags=copyright,importAssertNotNull,importSharedContainerConfig,sharedContainerConfig,testCreatePerson']
----
include::hotspots/src/test/java/io/openliberty/guides/testing/PersonServiceIT.1.java[]
----
PersonService.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/testing/PersonService.java[]
----
== Writing your first test
Now that the setup is complete, you can write your first test case. Start by testing the basic "create person" use case for your REST-based application. To test this use case, use the REST client that's injected by MicroShed Testing to make the HTTP POST request to the application and read the response.
[role="code_command hotspot file=0", subs="quotes"]
----
#Replace the `PersonServiceIT` class.#
`src/test/java/io/openliberty/guides/testing/PersonServiceIT.java`
----
PersonServiceIT.java
[source, java, linenums, role='code_column hide_tags=copyright,importSharedContainerConfig,sharedContainerConfig']
----
include::hotspots/src/test/java/io/openliberty/guides/testing/PersonServiceIT.1.java[]
----
Replace the `PersonServiceIT` class to include the [hotspot=importAssertNotNull file=0]`assertNotNull` static method and write the test logic in the [hotspot=testCreatePerson file=0]`testCreatePerson()` method.
Save the changes. Then, press the `enter/return` key in your console window to run the test. You see that the test ran again and exercised the REST endpoint of your application, including the response of your application's endpoint:
[role="no_copy"]
----
[INFO] Building rest client for class io.openliberty.guides.testing.PersonService with base path: http://localhost:9080/guide-microshed-testing/ and providers: [class org.microshed.testing.jaxrs.JsonBProvider]
[INFO] Response from server: 1809686877352335426
----
Next, add more tests.
[role="code_command hotspot file=1", subs="quotes"]
----
#Replace the `PersonServiceIT` class.#
`src/test/java/io/openliberty/guides/testing/PersonServiceIT.java`
----
PersonServiceIT.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::hotspots/src/test/java/io/openliberty/guides/testing/PersonServiceIT.2.java[]
----
The following tests are added: [hotspot=testMinSizeName file=1]`testMinSizeName()`, [hotspot=testMinAge file=1]`testMinAge()`, [hotspot=testGetPerson file=1]`testGetPerson()`, [hotspot=testGetAllPeople file=1]`testGetAllPeople()`, and [hotspot=testUpdateAge file=1]`testUpdateAge()`.
Save the changes, and press the `enter/return` key in your console window to run the tests.
== Testing outside of dev mode
Running tests in dev mode is convenient for local development, but it can be tedious to test against a running Open Liberty server in non-development scenarios such as CI/CD pipelines. For this reason, MicroShed Testing can start and stop the application runtime before and after the tests are run. This process is primarily accomplished by using Docker and Testcontainers.
To test outside of dev mode, exit dev mode by pressing `CTRL+C` in the command-line session where you ran the server.
Next, use the following Maven goal to run the tests from a cold start:
[role='command']
```
mvn verify
```
Running tests from a cold start takes a little longer than running tests from dev mode because the application runtime needs to start each time. However, tests that are run from a cold start use a clean instance on each run to ensure consistent results. These tests also automatically hook into existing build pipelines that are set up to run the `integration-test` phase.
== Sharing configuration across multiple classes
Typically, projects have multiple test classes that all use the same type of application deployment. For these cases, it's useful to reuse an existing configuration and application lifecycle across multiple test classes.
First, create another test class.
[role="code_command hotspot file=0", subs="quotes"]
----
#Create the `ErrorPathIT` class.#
`src/test/java/io/openliberty/guides/testing/ErrorPathIT.java`
----
ErrorPathIT.java
[source, java, linenums, role='code_column hide_tags=copyright,importSharedContainerConfig,sharedContainerConfig']
----
include::hotspots/src/test/java/io/openliberty/guides/testing/ErrorPathIT.java[]
----
The `ErrorPathIT` test class has the same [hotspot=container file=0]`@Container` configuration and [hotspot=personSvc file=0]`PersonService` REST client as the `PersonServiceIT` class.
Now, run the tests again outside of dev mode:
[role='command']
```
mvn verify
```
Notice that tests for both the `PersonServiceIT` and `ErrorPathIT` classes run, but a new server starts for each test class, resulting in a longer test runtime.
=== Creating a common configuration
To solve this issue, common configuration can be placed in a class that implements `SharedContainerConfiguration`.
[role="code_command hotspot file=0", subs="quotes"]
----
#Create the `AppDeploymentConfig` class.#
`src/test/java/io/openliberty/guides/testing/AppDeploymentConfig.java`
----
AppDeploymentConfig.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/test/java/io/openliberty/guides/testing/AppDeploymentConfig.java[]
----
After the common configuration is created, the test classes can be updated to reference this shared configuration.
=== Updating the PersonServiceIT class
ifndef::cloud-hosted[]
PersonServiceIT.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::hotspots/src/test/java/io/openliberty/guides/testing/PersonServiceIT.2.java[]
----
endif::[]
Remove the container code from the `PersonServiceIT` class. Remove [hotspot=importMPApp hotspot=importContainer file=0]`import` statements for `ApplicationContainer` and `Container` and the [hotspot=container hotspot=mpApp file=0]`ApplicationContainer app` field.
Next, annotate the `PersonServiceIT` class with the `@SharedContainerConfig` annotation that references the `AppDeploymentConfig` shared configuration class.
[role="code_command hotspot file=1", subs="quotes"]
----
#Replace the `PersonServiceIT` class.#
`src/test/java/io/openliberty/guides/testing/PersonServiceIT.java`
----
PersonServiceIT.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/test/java/io/openliberty/guides/testing/PersonServiceIT.java[]
----
Import the [hotspot=importSharedContainerConfig file=1]`SharedContainerConfig` annotation and annotate the `PersonServiceIT` class with [hotspot=sharedContainerConfig file=1]`@SharedContainerConfig`.
=== Updating the ErrorPathIT class
ifndef::cloud-hosted[]
ErrorPathIT.java
[source, java, linenums, role='code_column hide_tags=copyright,importSharedContainerConfig,sharedContainerConfig']
----
include::hotspots/src/test/java/io/openliberty/guides/testing/ErrorPathIT.java[]
----
endif::[]
Similarly, replace the `ErrorPathIT` class to remove the container code. Remove [hotspot=importMPApp hotspot=importContainer file=0]`import` statements for `ApplicationContainer` and `Container` and the [hotspot=container hotspot=mpApp file=0]`ApplicationContainer app` field.
Next, annotate the `ErrorPathIT` class with the `@SharedContainerConfig` annotation.
[role="code_command hotspot file=1", subs="quotes"]
----
#Replace the `ErrorPathIT` class.#
`src/test/java/io/openliberty/guides/testing/ErrorPathIT.java`
----
ErrorPathIT.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/test/java/io/openliberty/guides/testing/ErrorPathIT.java[]
----
Import the [hotspot=importSharedContainerConfig file=1]`SharedContainerConfig` annotation and annotate the `ErrorPathIT` class with [hotspot=sharedContainerConfig file=1]`@SharedContainerConfig`.
If you rerun the tests now, they run in about half the time because the same server instance is being used for both test classes:
[role='command']
```
mvn verify
```
== Great work! You're done!
You developed automated tests for a REST service in Open Liberty by using MicroShed Testing and Open Liberty dev mode.
== Related Links
Learn more about MicroShed Testing.
https://microshed.org/microshed-testing/[View the MicroShed Testing website^]
include::{common-includes}/attribution.adoc[subs="attributes"]