Testing a MicroProfile or Jakarta EE application

duration 20 minutes

Prerequisites:

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 MicroShed Testing to write tests for the application that exercise the application inside of a Docker container.

Sometimes tests might pass in development and testing (dev/test) environments, but fail in production because the application is running differently in production than it is in dev/test. Fortunately, you can minimize these parity issues between development 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 official Docker website.

To install Docker, follow the instructions in the official Docker documentation.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-microshed-testing.git
cd guide-microshed-testing

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

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 PersonServiceIT class to see what the tests look like:

PersonServiceIT.java

  1// tag::copyright[]
  2/*
  3 * Copyright (c) 2019 IBM Corporation and others
  4 *
  5 * See the NOTICE file(s) distributed with this work for additional
  6 * information regarding copyright ownership.
  7 *
  8 * Licensed under the Apache License, Version 2.0 (the "License");
  9 * You may not use this file except in compliance with the License.
 10 * You may obtain a copy of the License at
 11 *
 12 *     http://www.apache.org/licenses/LICENSE-2.0
 13 *
 14 * Unless required by applicable law or agreed to in writing, software
 15 * distributed under the License is distributed on an "AS IS" BASIS,
 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 17 * See the License for the specific language governing permissions and
 18 * limitations under the License.
 19 */
 20// end::copyright[]
 21package io.openliberty.guides.testing;
 22
 23import static org.junit.jupiter.api.Assertions.assertEquals;
 24import static org.junit.jupiter.api.Assertions.assertNotNull;
 25import static org.junit.jupiter.api.Assertions.assertTrue;
 26
 27import java.util.Collection;
 28
 29import javax.inject.Inject;
 30
 31import org.junit.jupiter.api.Test;
 32// tag::importSharedContainerConfig[]
 33import org.microshed.testing.SharedContainerConfig;
 34// end::importSharedContainerConfig[]
 35import org.microshed.testing.jupiter.MicroShedTest;
 36
 37@MicroShedTest
 38// tag::sharedContainerConfig[]
 39@SharedContainerConfig(AppDeploymentConfig.class)
 40// end::sharedContainerConfig[]
 41public class PersonServiceIT {
 42
 43    @Inject
 44    public static PersonService personSvc;
 45
 46    @Test
 47    public void testCreatePerson() {
 48        Long createId = personSvc.createPerson("Hank", 42);
 49        assertNotNull(createId);
 50    }
 51
 52    @Test
 53    public void testMinSizeName() {
 54        Long minSizeNameId = personSvc.createPerson("Ha", 42);
 55        assertEquals(new Person("Ha", 42, minSizeNameId),
 56                     personSvc.getPerson(minSizeNameId));
 57    }
 58
 59    @Test
 60    public void testMinAge() {
 61        Long minAgeId = personSvc.createPerson("Newborn", 0);
 62        assertEquals(new Person("Newborn", 0, minAgeId),
 63                     personSvc.getPerson(minAgeId));
 64    }
 65
 66    @Test
 67    public void testGetPerson() {
 68        Long bobId = personSvc.createPerson("Bob", 24);
 69        Person bob = personSvc.getPerson(bobId);
 70        assertEquals("Bob", bob.name);
 71        assertEquals(24, bob.age);
 72        assertNotNull(bob.id);
 73    }
 74
 75    @Test
 76    public void testGetAllPeople() {
 77        Long person1Id = personSvc.createPerson("Person1", 1);
 78        Long person2Id = personSvc.createPerson("Person2", 2);
 79
 80        Person expected1 = new Person("Person1", 1, person1Id);
 81        Person expected2 = new Person("Person2", 2, person2Id);
 82
 83        Collection<Person> allPeople = personSvc.getAllPeople();
 84        assertTrue(allPeople.size() >= 2,
 85            "Expected at least 2 people to be registered, but there were only: " +
 86            allPeople);
 87        assertTrue(allPeople.contains(expected1),
 88            "Did not find person " + expected1 + " in all people: " + allPeople);
 89        assertTrue(allPeople.contains(expected2),
 90            "Did not find person " + expected2 + " in all people: " + allPeople);
 91    }
 92
 93    @Test
 94    public void testUpdateAge() {
 95        Long personId = personSvc.createPerson("newAgePerson", 1);
 96
 97        Person originalPerson = personSvc.getPerson(personId);
 98        assertEquals("newAgePerson", originalPerson.name);
 99        assertEquals(1, originalPerson.age);
100        assertEquals(personId, Long.valueOf(originalPerson.id));
101
102        personSvc.updatePerson(personId,
103            new Person(originalPerson.name, 2, originalPerson.id));
104        Person updatedPerson = personSvc.getPerson(personId);
105        assertEquals("newAgePerson", updatedPerson.name);
106        assertEquals(2, updatedPerson.age);
107        assertEquals(personId, Long.valueOf(updatedPerson.id));
108    }
109}

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:

mvn verify

This command might take some time to run the first time 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 development mode, you can use MicroShed Testing to run tests on an already running Open Liberty server. Run the following Maven goal to start Open Liberty in development mode:

mvn liberty:dev

After the Open Liberty server starts and you see the Press the Enter key to run tests on demand. 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. Development mode automatically recompiles and updates any application or test code changes that you make.

After you are finished running tests, stop the Open Liberty server by typing q in the shell session where you ran the server, and then press the enter/return key.

Bootstrapping your application for testing

Navigate to the start directory to begin.

Start Open Liberty in development mode, which starts the Open Liberty server and listens for file changes:

mvn liberty:dev

Wait for the Press the Enter key to run tests on demand. message, and then press the enter/return key to run the tests. You see that one test runs:

[INFO] Running integration tests...
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running io.openliberty.guides.testing.PersonServiceIT
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s - in io.openliberty.guides.testing.PersonServiceIT
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] Integration tests finished.

To begin bootstrapping, annotate the src/test/java/io/openliberty/guides/testing/PersonServiceIT.java class with the @MicroShedTest annotation. This annotation indicates that the test class uses MicroShed Testing.

Update the PersonServiceIT class.
src/test/java/io/openliberty/guides/testing/PersonServiceIT.java

Import the MicroShedTest annotation and annotate the PersonServiceIT class with @MicroShedTest.

PersonServiceIT.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23import org.junit.jupiter.api.Test;
24// tag::importMicroShedTest[]
25import org.microshed.testing.jupiter.MicroShedTest;
26// end::importMicroShedTest[]
27
28// tag::microShedTest[]
29@MicroShedTest
30// end::microShedTest[]
31public class PersonServiceIT {
32
33    @Test
34    public void testCreatePerson() {
35    }
36
37}

Next, the PersonServiceIT class outlines some basic information that informs how MicroShed Testing starts the application runtime and at which URL path the application will be available:

Update the PersonServiceIT class.
src/test/java/io/openliberty/guides/testing/PersonServiceIT.java

Import the MicroProfileApplication class and the Container annotation, create the MicroProfileApplication application, and annotate the application with @Container.

PersonServiceIT.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23// tag::importAssertNotNull[]
24import static org.junit.jupiter.api.Assertions.assertNotNull;
25// end::importAssertNotNull[]
26
27// tag::importInject[]
28import javax.inject.Inject;
29// end::importInject[]
30
31import org.junit.jupiter.api.Test;
32// tag::importMicroShedTest[]
33import org.microshed.testing.jupiter.MicroShedTest;
34// end::importMicroShedTest[]
35// tag::importSharedContainerConfig[]
36import org.microshed.testing.SharedContainerConfig;
37// end::importSharedContainerConfig[]
38// tag::importMPApp[]
39import org.microshed.testing.testcontainers.MicroProfileApplication;
40// end::importMPApp[]
41// tag::importContainer[]
42import org.testcontainers.junit.jupiter.Container;
43// end::importContainer[]
44
45// tag::microShedTest[]
46@MicroShedTest
47// end::microShedTest[]
48// tag::sharedContainerConfig[]
49@SharedContainerConfig(AppDeploymentConfig.class)
50// end::sharedContainerConfig[]
51public class PersonServiceIT {
52
53    // tag::inject[]
54    @Inject
55    // end::inject[]
56    // tag::personSvc[]
57    public static PersonService personSvc;
58    // end::personSvc[]
59
60    // tag::container[]
61    @Container
62    // end::container[]
63    // tag::mpApp[]
64    public static MicroProfileApplication app = new MicroProfileApplication()
65                    // tag::withAppContextRoot[]
66                    .withAppContextRoot("/guide-microshed-testing")
67                    // end::withAppContextRoot[]
68                    // tag::withReadinessPath[]
69                    .withReadinessPath("/health/ready");
70                    // end::withReadinessPath[]
71    // end::mpApp[]
72
73    @Test
74    public void testCreatePerson() {
75        // tag::testCreatePerson[]
76        Long createId = personSvc.createPerson("Hank", 42);
77        assertNotNull(createId);
78        // end::testCreatePerson[]
79    }
80
81}

The 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 /guide-microshed-testing.

The withReadinessPath(String) method indicates what path is polled by HTTP to determine application readiness. MicroShed Testing automatically starts the MicroProfileApplication application and waits for it to be ready before the tests start running. In this case, you are using the default application readiness check at the http://localhost:9080/health/ready URL, which is enabled by the mpHealth-2.0 feature in our server.xml configuration file. When the readiness URL returns HTTP 200, the application is considered ready and the tests begin running.

server.xml

 1<server>
 2
 3    <featureManager>
 4        <feature>jaxrs-2.1</feature>
 5        <feature>jsonb-1.0</feature>
 6        <!-- tag::mpHealth[] -->
 7        <feature>mpHealth-2.0</feature>
 8        <!-- end::mpHealth[] -->
 9        <feature>mpConfig-1.3</feature>
10        <feature>mpRestClient-1.1</feature>
11        <feature>beanValidation-2.0</feature>
12        <feature>cdi-2.0</feature>
13    </featureManager>
14
15</server>

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 cannot access the application internals. Instead, the application is exercised from the outside, usually with HTTP requests. To simplify the HTTP interactions, inject a REST client into the tests.

Update the PersonServiceIT class.
src/test/java/io/openliberty/guides/testing/PersonServiceIT.java

Import the Inject annotation, create a PersonService REST client, and annotate the REST client with @Inject.

PersonServiceIT.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23// tag::importAssertNotNull[]
24import static org.junit.jupiter.api.Assertions.assertNotNull;
25// end::importAssertNotNull[]
26
27// tag::importInject[]
28import javax.inject.Inject;
29// end::importInject[]
30
31import org.junit.jupiter.api.Test;
32// tag::importMicroShedTest[]
33import org.microshed.testing.jupiter.MicroShedTest;
34// end::importMicroShedTest[]
35// tag::importSharedContainerConfig[]
36import org.microshed.testing.SharedContainerConfig;
37// end::importSharedContainerConfig[]
38// tag::importMPApp[]
39import org.microshed.testing.testcontainers.MicroProfileApplication;
40// end::importMPApp[]
41// tag::importContainer[]
42import org.testcontainers.junit.jupiter.Container;
43// end::importContainer[]
44
45// tag::microShedTest[]
46@MicroShedTest
47// end::microShedTest[]
48// tag::sharedContainerConfig[]
49@SharedContainerConfig(AppDeploymentConfig.class)
50// end::sharedContainerConfig[]
51public class PersonServiceIT {
52
53    // tag::inject[]
54    @Inject
55    // end::inject[]
56    // tag::personSvc[]
57    public static PersonService personSvc;
58    // end::personSvc[]
59
60    // tag::container[]
61    @Container
62    // end::container[]
63    // tag::mpApp[]
64    public static MicroProfileApplication app = new MicroProfileApplication()
65                    // tag::withAppContextRoot[]
66                    .withAppContextRoot("/guide-microshed-testing")
67                    // end::withAppContextRoot[]
68                    // tag::withReadinessPath[]
69                    .withReadinessPath("/health/ready");
70                    // end::withReadinessPath[]
71    // end::mpApp[]
72
73    @Test
74    public void testCreatePerson() {
75        // tag::testCreatePerson[]
76        Long createId = personSvc.createPerson("Hank", 42);
77        assertNotNull(createId);
78        // end::testCreatePerson[]
79    }
80
81}

In this example, the PersonService injected type is the same 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, which triggers the corresponding Java method in the application.

PersonService.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23import java.util.Collection;
24import java.util.HashMap;
25import java.util.Map;
26
27import javax.enterprise.context.ApplicationScoped;
28import javax.validation.Valid;
29import javax.validation.constraints.NotEmpty;
30import javax.validation.constraints.PositiveOrZero;
31import javax.validation.constraints.Size;
32import javax.ws.rs.Consumes;
33import javax.ws.rs.DELETE;
34import javax.ws.rs.GET;
35import javax.ws.rs.NotFoundException;
36import javax.ws.rs.POST;
37import javax.ws.rs.Path;
38import javax.ws.rs.PathParam;
39import javax.ws.rs.Produces;
40import javax.ws.rs.QueryParam;
41import javax.ws.rs.core.MediaType;
42
43@Path("/people")
44@ApplicationScoped
45@Produces(MediaType.APPLICATION_JSON)
46@Consumes(MediaType.APPLICATION_JSON)
47public class PersonService {
48
49    private final Map<Long, Person> personRepo = new HashMap<>();
50
51    @GET
52    public Collection<Person> getAllPeople() {
53        return personRepo.values();
54    }
55
56    @GET
57    @Path("/{personId}")
58    public Person getPerson(@PathParam("personId") long id) {
59        Person foundPerson = personRepo.get(id);
60        if (foundPerson == null)
61            personNotFound(id);
62        return foundPerson;
63    }
64
65    @POST
66    public Long createPerson(@QueryParam("name") @NotEmpty @Size(min=2, max=50) String name,
67                             @QueryParam("age") @PositiveOrZero int age) {
68        Person p = new Person(name, age);
69        personRepo.put(p.id, p);
70        return p.id;
71    }
72
73    @POST
74    @Path("/{personId}")
75    public void updatePerson(@PathParam("personId") long id, @Valid Person p) {
76        Person toUpdate = getPerson(id);
77        if (toUpdate == null)
78            personNotFound(id);
79        personRepo.put(id, p);
80    }
81
82    @DELETE
83    @Path("/{personId}")
84    public void removePerson(@PathParam("personId") long id) {
85        Person toDelete = personRepo.get(id);
86        if (toDelete == null)
87            personNotFound(id);
88        personRepo.remove(id);
89    }
90
91    private void personNotFound(long id) {
92        throw new NotFoundException("Person with id " + id + " not found.");
93    }
94
95}

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.

Update the PersonServiceIT class.
src/test/java/io/openliberty/guides/testing/PersonServiceIT.java

Import the assertNotNull static method and write the test logic in the testCreatePerson() method.

PersonServiceIT.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23// tag::importAssertNotNull[]
24import static org.junit.jupiter.api.Assertions.assertNotNull;
25// end::importAssertNotNull[]
26
27// tag::importInject[]
28import javax.inject.Inject;
29// end::importInject[]
30
31import org.junit.jupiter.api.Test;
32// tag::importMicroShedTest[]
33import org.microshed.testing.jupiter.MicroShedTest;
34// end::importMicroShedTest[]
35// tag::importSharedContainerConfig[]
36import org.microshed.testing.SharedContainerConfig;
37// end::importSharedContainerConfig[]
38// tag::importMPApp[]
39import org.microshed.testing.testcontainers.MicroProfileApplication;
40// end::importMPApp[]
41// tag::importContainer[]
42import org.testcontainers.junit.jupiter.Container;
43// end::importContainer[]
44
45// tag::microShedTest[]
46@MicroShedTest
47// end::microShedTest[]
48// tag::sharedContainerConfig[]
49@SharedContainerConfig(AppDeploymentConfig.class)
50// end::sharedContainerConfig[]
51public class PersonServiceIT {
52
53    // tag::inject[]
54    @Inject
55    // end::inject[]
56    // tag::personSvc[]
57    public static PersonService personSvc;
58    // end::personSvc[]
59
60    // tag::container[]
61    @Container
62    // end::container[]
63    // tag::mpApp[]
64    public static MicroProfileApplication app = new MicroProfileApplication()
65                    // tag::withAppContextRoot[]
66                    .withAppContextRoot("/guide-microshed-testing")
67                    // end::withAppContextRoot[]
68                    // tag::withReadinessPath[]
69                    .withReadinessPath("/health/ready");
70                    // end::withReadinessPath[]
71    // end::mpApp[]
72
73    @Test
74    public void testCreatePerson() {
75        // tag::testCreatePerson[]
76        Long createId = personSvc.createPerson("Hank", 42);
77        assertNotNull(createId);
78        // end::testCreatePerson[]
79    }
80
81}

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:

INFO org.microshed.testing.jaxrs.RestClientBuilder  - 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 org.microshed.testing.jaxrs.JsonBProvider  - Response from server: 1809686877352335426

Next, add more tests.

Replace the PersonServiceIT class.
src/test/java/io/openliberty/guides/testing/PersonServiceIT.java

PersonServiceIT.java

  1// tag::copyright[]
  2/*
  3 * Copyright (c) 2019 IBM Corporation and others
  4 *
  5 * See the NOTICE file(s) distributed with this work for additional
  6 * information regarding copyright ownership.
  7 *
  8 * Licensed under the Apache License, Version 2.0 (the "License");
  9 * You may not use this file except in compliance with the License.
 10 * You may obtain a copy of the License at
 11 *
 12 *     http://www.apache.org/licenses/LICENSE-2.0
 13 *
 14 * Unless required by applicable law or agreed to in writing, software
 15 * distributed under the License is distributed on an "AS IS" BASIS,
 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 17 * See the License for the specific language governing permissions and
 18 * limitations under the License.
 19 */
 20// end::copyright[]
 21package io.openliberty.guides.testing;
 22
 23import static org.junit.jupiter.api.Assertions.assertEquals;
 24import static org.junit.jupiter.api.Assertions.assertNotNull;
 25import static org.junit.jupiter.api.Assertions.assertTrue;
 26
 27import java.util.Collection;
 28
 29import javax.inject.Inject;
 30
 31import org.junit.jupiter.api.Test;
 32import org.microshed.testing.jupiter.MicroShedTest;
 33import org.microshed.testing.testcontainers.MicroProfileApplication;
 34import org.testcontainers.junit.jupiter.Container;
 35
 36@MicroShedTest
 37public class PersonServiceIT {
 38
 39    @Inject
 40    public static PersonService personSvc;
 41
 42    @Container
 43    public static MicroProfileApplication app = new MicroProfileApplication()
 44                    .withAppContextRoot("/guide-microshed-testing")
 45                    .withReadinessPath("/health/ready");
 46
 47    @Test
 48    public void testCreatePerson() {
 49        Long createId = personSvc.createPerson("Hank", 42);
 50        assertNotNull(createId);
 51    }
 52
 53    // tag::tests[]
 54    // tag::testMinSizeName[]
 55    @Test
 56    public void testMinSizeName() {
 57        Long minSizeNameId = personSvc.createPerson("Ha", 42);
 58        assertEquals(new Person("Ha", 42, minSizeNameId),
 59                     personSvc.getPerson(minSizeNameId));
 60    }
 61    // end::testMinSizeName[]
 62
 63    // tag::testMinAge[]
 64    @Test
 65    public void testMinAge() {
 66        Long minAgeId = personSvc.createPerson("Newborn", 0);
 67        assertEquals(new Person("Newborn", 0, minAgeId),
 68                     personSvc.getPerson(minAgeId));
 69    }
 70    // end::testMinAge[]
 71
 72    // tag::testGetPerson[]
 73    @Test
 74    public void testGetPerson() {
 75        Long bobId = personSvc.createPerson("Bob", 24);
 76        Person bob = personSvc.getPerson(bobId);
 77        assertEquals("Bob", bob.name);
 78        assertEquals(24, bob.age);
 79        assertNotNull(bob.id);
 80    }
 81    // end::testGetPerson[]
 82
 83    // tag::testGetAllPeople[]
 84    @Test
 85    public void testGetAllPeople() {
 86        Long person1Id = personSvc.createPerson("Person1", 1);
 87        Long person2Id = personSvc.createPerson("Person2", 2);
 88
 89        Person expected1 = new Person("Person1", 1, person1Id);
 90        Person expected2 = new Person("Person2", 2, person2Id);
 91
 92        Collection<Person> allPeople = personSvc.getAllPeople();
 93        assertTrue(allPeople.size() >= 2,
 94            "Expected at least 2 people to be registered, but there were only: " +
 95            allPeople);
 96        assertTrue(allPeople.contains(expected1),
 97            "Did not find person " + expected1 + " in all people: " + allPeople);
 98        assertTrue(allPeople.contains(expected2),
 99            "Did not find person " + expected2 + " in all people: " + allPeople);
100    }
101    // end::testGetAllPeople[]
102
103    // tag::testUpdateAge[]
104    @Test
105    public void testUpdateAge() {
106        Long personId = personSvc.createPerson("newAgePerson", 1);
107
108        Person originalPerson = personSvc.getPerson(personId);
109        assertEquals("newAgePerson", originalPerson.name);
110        assertEquals(1, originalPerson.age);
111        assertEquals(personId, Long.valueOf(originalPerson.id));
112
113        personSvc.updatePerson(personId,
114            new Person(originalPerson.name, 2, originalPerson.id));
115        Person updatedPerson = personSvc.getPerson(personId);
116        assertEquals("newAgePerson", updatedPerson.name);
117        assertEquals(2, updatedPerson.age);
118        assertEquals(personId, Long.valueOf(updatedPerson.id));
119    }
120    // end::testUpdateAge[]
121    // end::tests[]
122}

The following tests are added: testMinSizeName(), testMinAge(), testGetPerson(), testGetAllPeople(), and testUpdateAge().

Save the changes, and press the enter/return key in your console window to run the tests.

Testing outside of development mode

Running tests in development 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 development mode, exit development mode by typing q in the shell session where you ran the server, and then press the enter/return key.

Next, use the following Maven goal to run the tests from a cold start:

mvn verify

Running tests from a cold start takes a little longer than running tests from development 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 is useful to reuse an existing configuration and application lifecycle across multiple test classes.

First, create another test class.

Create the ErrorPathIT class.
src/test/java/io/openliberty/guides/testing/ErrorPathIT.java

ErrorPathIT.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23import static org.junit.jupiter.api.Assertions.assertThrows;
24
25import javax.inject.Inject;
26import javax.ws.rs.BadRequestException;
27import javax.ws.rs.NotFoundException;
28
29import org.junit.jupiter.api.Test;
30import org.microshed.testing.jupiter.MicroShedTest;
31// tag::importSharedContainerConfig[]
32import org.microshed.testing.SharedContainerConfig;
33// end::importSharedContainerConfig[]
34// tag::importMPApp[]
35import org.microshed.testing.testcontainers.MicroProfileApplication;
36// end::importMPApp[]
37// tag::importContainer[]
38import org.testcontainers.junit.jupiter.Container;
39// end::importContainer[]
40
41@MicroShedTest
42// tag::sharedContainerConfig[]
43@SharedContainerConfig(AppDeploymentConfig.class)
44// end::sharedContainerConfig[]
45public class ErrorPathIT {
46
47    // tag::container[]
48    @Container
49    public static MicroProfileApplication app = new MicroProfileApplication()
50                    .withAppContextRoot("/guide-microshed-testing")
51                    .withReadinessPath("/health/ready");
52    // end::container[]
53
54    // tag::personSvc[]
55    @Inject
56    public static PersonService personSvc;
57    // end::personSvc[]
58
59    @Test
60    public void testGetUnknownPerson() {
61        assertThrows(NotFoundException.class, () -> personSvc.getPerson(-1L));
62    }
63
64    @Test
65    public void testCreateBadPersonNullName() {
66        assertThrows(BadRequestException.class, () -> personSvc.createPerson(null, 5));
67    }
68
69    @Test
70    public void testCreateBadPersonNegativeAge() {
71        assertThrows(BadRequestException.class, () ->
72          personSvc.createPerson("NegativeAgePersoN", -1));
73    }
74
75    @Test
76    public void testCreateBadPersonNameTooLong() {
77        assertThrows(BadRequestException.class, () ->
78          personSvc.createPerson("NameTooLongPersonNameTooLongPersonNameTooLongPerson",
79          5));
80    }
81}

The ErrorPathIT test class has the same @Container configuration and PersonService REST client as the PersonServiceIT class.

Now, run the tests again outside of development mode:

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.

To solve this issue, common configuration can be placed in a class that implements SharedContainerConfiguration.

Create the AppDeploymentConfig class.
src/test/java/io/openliberty/guides/testing/AppDeploymentConfig.java

AppDeploymentConfig.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23import org.microshed.testing.SharedContainerConfiguration;
24import org.microshed.testing.testcontainers.MicroProfileApplication;
25import org.testcontainers.junit.jupiter.Container;
26
27public class AppDeploymentConfig implements SharedContainerConfiguration {
28
29    @Container
30    public static MicroProfileApplication app = new MicroProfileApplication()
31                    .withAppContextRoot("/guide-microshed-testing")
32                    .withReadinessPath("/health/ready");
33
34}

After the common configuration is created, the test classes can be updated to reference this shared configuration.

Remove the container code from the PersonServiceIT class.

Update the PersonServiceIT class.
src/test/java/io/openliberty/guides/testing/PersonServiceIT.java

Remove import statements and the MicroProfileApplication app field.

PersonServiceIT.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23// tag::importAssertNotNull[]
24import static org.junit.jupiter.api.Assertions.assertNotNull;
25// end::importAssertNotNull[]
26
27// tag::importInject[]
28import javax.inject.Inject;
29// end::importInject[]
30
31import org.junit.jupiter.api.Test;
32// tag::importMicroShedTest[]
33import org.microshed.testing.jupiter.MicroShedTest;
34// end::importMicroShedTest[]
35// tag::importSharedContainerConfig[]
36import org.microshed.testing.SharedContainerConfig;
37// end::importSharedContainerConfig[]
38// tag::importMPApp[]
39import org.microshed.testing.testcontainers.MicroProfileApplication;
40// end::importMPApp[]
41// tag::importContainer[]
42import org.testcontainers.junit.jupiter.Container;
43// end::importContainer[]
44
45// tag::microShedTest[]
46@MicroShedTest
47// end::microShedTest[]
48// tag::sharedContainerConfig[]
49@SharedContainerConfig(AppDeploymentConfig.class)
50// end::sharedContainerConfig[]
51public class PersonServiceIT {
52
53    // tag::inject[]
54    @Inject
55    // end::inject[]
56    // tag::personSvc[]
57    public static PersonService personSvc;
58    // end::personSvc[]
59
60    // tag::container[]
61    @Container
62    // end::container[]
63    // tag::mpApp[]
64    public static MicroProfileApplication app = new MicroProfileApplication()
65                    // tag::withAppContextRoot[]
66                    .withAppContextRoot("/guide-microshed-testing")
67                    // end::withAppContextRoot[]
68                    // tag::withReadinessPath[]
69                    .withReadinessPath("/health/ready");
70                    // end::withReadinessPath[]
71    // end::mpApp[]
72
73    @Test
74    public void testCreatePerson() {
75        // tag::testCreatePerson[]
76        Long createId = personSvc.createPerson("Hank", 42);
77        assertNotNull(createId);
78        // end::testCreatePerson[]
79    }
80
81}

Annotate the PersonServiceIT class with the @SharedContainerConfiguration annotation that references the AppDeploymentConfig shared configuration class.

Update the PersonServiceIT class.
src/test/java/io/openliberty/guides/testing/PersonServiceIT.java

Import the SharedContainerConfig annotation and annotate the PersonServiceIT class with @SharedContainerConfig.

PersonServiceIT.java

  1// tag::copyright[]
  2/*
  3 * Copyright (c) 2019 IBM Corporation and others
  4 *
  5 * See the NOTICE file(s) distributed with this work for additional
  6 * information regarding copyright ownership.
  7 *
  8 * Licensed under the Apache License, Version 2.0 (the "License");
  9 * You may not use this file except in compliance with the License.
 10 * You may obtain a copy of the License at
 11 *
 12 *     http://www.apache.org/licenses/LICENSE-2.0
 13 *
 14 * Unless required by applicable law or agreed to in writing, software
 15 * distributed under the License is distributed on an "AS IS" BASIS,
 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 17 * See the License for the specific language governing permissions and
 18 * limitations under the License.
 19 */
 20// end::copyright[]
 21package io.openliberty.guides.testing;
 22
 23import static org.junit.jupiter.api.Assertions.assertEquals;
 24import static org.junit.jupiter.api.Assertions.assertNotNull;
 25import static org.junit.jupiter.api.Assertions.assertTrue;
 26
 27import java.util.Collection;
 28
 29import javax.inject.Inject;
 30
 31import org.junit.jupiter.api.Test;
 32// tag::importSharedContainerConfig[]
 33import org.microshed.testing.SharedContainerConfig;
 34// end::importSharedContainerConfig[]
 35import org.microshed.testing.jupiter.MicroShedTest;
 36
 37@MicroShedTest
 38// tag::sharedContainerConfig[]
 39@SharedContainerConfig(AppDeploymentConfig.class)
 40// end::sharedContainerConfig[]
 41public class PersonServiceIT {
 42
 43    @Inject
 44    public static PersonService personSvc;
 45
 46    @Test
 47    public void testCreatePerson() {
 48        Long createId = personSvc.createPerson("Hank", 42);
 49        assertNotNull(createId);
 50    }
 51
 52    @Test
 53    public void testMinSizeName() {
 54        Long minSizeNameId = personSvc.createPerson("Ha", 42);
 55        assertEquals(new Person("Ha", 42, minSizeNameId),
 56                     personSvc.getPerson(minSizeNameId));
 57    }
 58
 59    @Test
 60    public void testMinAge() {
 61        Long minAgeId = personSvc.createPerson("Newborn", 0);
 62        assertEquals(new Person("Newborn", 0, minAgeId),
 63                     personSvc.getPerson(minAgeId));
 64    }
 65
 66    @Test
 67    public void testGetPerson() {
 68        Long bobId = personSvc.createPerson("Bob", 24);
 69        Person bob = personSvc.getPerson(bobId);
 70        assertEquals("Bob", bob.name);
 71        assertEquals(24, bob.age);
 72        assertNotNull(bob.id);
 73    }
 74
 75    @Test
 76    public void testGetAllPeople() {
 77        Long person1Id = personSvc.createPerson("Person1", 1);
 78        Long person2Id = personSvc.createPerson("Person2", 2);
 79
 80        Person expected1 = new Person("Person1", 1, person1Id);
 81        Person expected2 = new Person("Person2", 2, person2Id);
 82
 83        Collection<Person> allPeople = personSvc.getAllPeople();
 84        assertTrue(allPeople.size() >= 2,
 85            "Expected at least 2 people to be registered, but there were only: " +
 86            allPeople);
 87        assertTrue(allPeople.contains(expected1),
 88            "Did not find person " + expected1 + " in all people: " + allPeople);
 89        assertTrue(allPeople.contains(expected2),
 90            "Did not find person " + expected2 + " in all people: " + allPeople);
 91    }
 92
 93    @Test
 94    public void testUpdateAge() {
 95        Long personId = personSvc.createPerson("newAgePerson", 1);
 96
 97        Person originalPerson = personSvc.getPerson(personId);
 98        assertEquals("newAgePerson", originalPerson.name);
 99        assertEquals(1, originalPerson.age);
100        assertEquals(personId, Long.valueOf(originalPerson.id));
101
102        personSvc.updatePerson(personId,
103            new Person(originalPerson.name, 2, originalPerson.id));
104        Person updatedPerson = personSvc.getPerson(personId);
105        assertEquals("newAgePerson", updatedPerson.name);
106        assertEquals(2, updatedPerson.age);
107        assertEquals(personId, Long.valueOf(updatedPerson.id));
108    }
109}

Similarly, update the ErrorPathIT class to remove the container code.

Update the ErrorPathIT class.
src/test/java/io/openliberty/guides/testing/ErrorPathIT.java

Remove import statements and the MicroProfileApplication app field

ErrorPathIT.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23import static org.junit.jupiter.api.Assertions.assertThrows;
24
25import javax.inject.Inject;
26import javax.ws.rs.BadRequestException;
27import javax.ws.rs.NotFoundException;
28
29import org.junit.jupiter.api.Test;
30import org.microshed.testing.jupiter.MicroShedTest;
31// tag::importSharedContainerConfig[]
32import org.microshed.testing.SharedContainerConfig;
33// end::importSharedContainerConfig[]
34// tag::importMPApp[]
35import org.microshed.testing.testcontainers.MicroProfileApplication;
36// end::importMPApp[]
37// tag::importContainer[]
38import org.testcontainers.junit.jupiter.Container;
39// end::importContainer[]
40
41@MicroShedTest
42// tag::sharedContainerConfig[]
43@SharedContainerConfig(AppDeploymentConfig.class)
44// end::sharedContainerConfig[]
45public class ErrorPathIT {
46
47    // tag::container[]
48    @Container
49    public static MicroProfileApplication app = new MicroProfileApplication()
50                    .withAppContextRoot("/guide-microshed-testing")
51                    .withReadinessPath("/health/ready");
52    // end::container[]
53
54    // tag::personSvc[]
55    @Inject
56    public static PersonService personSvc;
57    // end::personSvc[]
58
59    @Test
60    public void testGetUnknownPerson() {
61        assertThrows(NotFoundException.class, () -> personSvc.getPerson(-1L));
62    }
63
64    @Test
65    public void testCreateBadPersonNullName() {
66        assertThrows(BadRequestException.class, () -> personSvc.createPerson(null, 5));
67    }
68
69    @Test
70    public void testCreateBadPersonNegativeAge() {
71        assertThrows(BadRequestException.class, () ->
72          personSvc.createPerson("NegativeAgePersoN", -1));
73    }
74
75    @Test
76    public void testCreateBadPersonNameTooLong() {
77        assertThrows(BadRequestException.class, () ->
78          personSvc.createPerson("NameTooLongPersonNameTooLongPersonNameTooLongPerson",
79          5));
80    }
81}

Annotate the ErrorPathIT class with the @SharedContainerConfiguration annotation.

Update the ErrorPathIT class.
src/test/java/io/openliberty/guides/testing/ErrorPathIT.java

Import the SharedContainerConfig annotation and annotate the ErrorPathIT class with @SharedContainerConfig.

ErrorPathIT.java

 1// tag::copyright[]
 2/*
 3 * Copyright (c) 2019 IBM Corporation and others
 4 *
 5 * See the NOTICE file(s) distributed with this work for additional
 6 * information regarding copyright ownership.
 7 *
 8 * Licensed under the Apache License, Version 2.0 (the "License");
 9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20// end::copyright[]
21package io.openliberty.guides.testing;
22
23import static org.junit.jupiter.api.Assertions.assertThrows;
24
25import javax.inject.Inject;
26import javax.ws.rs.BadRequestException;
27import javax.ws.rs.NotFoundException;
28
29import org.junit.jupiter.api.Test;
30// tag::importSharedContainerConfig[]
31import org.microshed.testing.SharedContainerConfig;
32// end::importSharedContainerConfig[]
33import org.microshed.testing.jupiter.MicroShedTest;
34
35@MicroShedTest
36// tag::sharedContainerConfig[]
37@SharedContainerConfig(AppDeploymentConfig.class)
38// end::sharedContainerConfig[]
39public class ErrorPathIT {
40
41    @Inject
42    public static PersonService personSvc;
43
44    @Test
45    public void testGetUnknownPerson() {
46        assertThrows(NotFoundException.class, () -> personSvc.getPerson(-1L));
47    }
48
49    @Test
50    public void testCreateBadPersonNullName() {
51        assertThrows(BadRequestException.class, () -> personSvc.createPerson(null, 5));
52    }
53
54    @Test
55    public void testCreateBadPersonNegativeAge() {
56        assertThrows(BadRequestException.class, () ->
57          personSvc.createPerson("NegativeAgePersoN", -1));
58    }
59
60    @Test
61    public void testCreateBadPersonNameTooLong() {
62        assertThrows(BadRequestException.class, () ->
63           personSvc.createPerson("NameTooLongPersonNameTooLongPersonNameTooLongPerson",
64           5));
65    }
66}

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:

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 development mode.

Learn more about MicroShed Testing.

Guide Attribution

Testing a MicroProfile or Jakarta EE application by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star