Slide 1

Slide 1 text

1 Testing with Eclipse GlassFish David Matějček Owner & Angry QA & Developer OmniFish Eclipse GlassFish Lead

Slide 2

Slide 2 text

2 Good Old Times … …? 2005 Sun Java Application Server 8 Java EE was born, standardization. Extremely buggy version Unfinished prototyping 2009 Sun GlassFish Enterprise Server 2.1.1.2 Production ready again 1997 Netscape iPlanet 2003 SunOne 7 Quite reliable, WebArchive (war), Single UI manages several instances. Enterprise solution, commercial Java version required. 2011 Oracle GlassFish 3.1 Java EE 6 Users started losing patience 2017 Oracle GlassFish 5.0 Java EE 8 Very buggy

Slide 3

Slide 3 text

3 Rescue of Java EE Platform: Eclipse Foundation 2020 Eclipse GlassFish 6 Jakarta EE train gains speed Remaining community evaluates 2022 Eclipse GlassFish 7 Production ready again Jakarta EE 10 Java 11-23 supported (17 rec.) Stable development Bugfixes Improvements Refactoring Commercial support Community is slowly growing 2017 Oracle donated GlassFish Why? To be able to validate JEE Specifications 2019 Eclipse GlassFish 5.1 Same as Oracle GlassFish 5.0, but under Eclipse Foundation 2025 Eclipse GlassFish 8 Jakarta EE 11 Java 21+ supported Already releasing milestones To Be Continued …

Slide 4

Slide 4 text

Testing Java EE Applications 2005 ● We had a Java EE Platform making things easier … but without any support for automated tests … making things pretty uneasy ● Ejb3Unit (died too soon, supported just simple features) ● EasyMock, Mockito (but mocks were so complicated!) ● Integration Tests (but the server was always still running!) ● GlassFish used around 65000 integration tests: usually small war application testing single scenario ○ How long could it run in 2005???

Slide 5

Slide 5 text

EasyMock vs. HttpServletRequest import static org.easymock .EasyMock .createNiceMock; import static org.easymock .EasyMock .expect; import static org.easymock .EasyMock .isA; import static org.easymock .EasyMock .replay; import static org.junit.jupiter.api.Assertions .assertEquals; public class EasyMockTest { @Test public void getParameter() throws Exception { HttpServletRequest request = createNiceMock(HttpServletRequest .class); expect(request.getParameter(isA(String.class))).andStubReturn("anything"); expect(request.getMethod()).andStubReturn("GET"); replay(request); assertEquals("anything", request.getParameter("parameterName")); } } 5

Slide 6

Slide 6 text

Testing Jakarta EE Applications 2024 ● We still have a Jakarta EE Platform making things easier ● We have much better support many people don’t know ● JUnit5, EasyMock, Mockito, Weld-Junit, HK2 based tests, TestContainers, GlassFish Test Utilities, JMH, Selenium, Embedded GlassFish … and their combinations!!! ● Integration Tests ○ GlassFish still uses around 65000 integration tests ○ Execution take some 8 hours ○ Can run them locally

Slide 7

Slide 7 text

Testing with JUnit5 7 Junit5 Extension Deploy - Undeploy Configure - reset Defines actions for test classes and methods Uses environment Can be combined 3 Global Environment System dependencies, file system, ports, network interfaces, cpu, memory, … resources. 4 Test Method Send Input to the application, verify results @BeforeEach, @AfterEach, 1 Test Class Defines some unit @BeforeAll, @AfterAll 2

Slide 8

Slide 8 text

Weld-Junit or HK2? ● Partially support injections ● You can customize - ie. to fake Jakarta Transactions support or mock SQL database ● Weld directly targets unit tests ● HK2 is partial CDI implementation

Slide 9

Slide 9 text

Weld-Junit or HK2? @ExtendWith(WeldJunit5Extension.class) class SimpleTest { @WeldSetup public WeldInitiator weld = WeldInitiator.of(Foo.class); @Test public void testFoo() { // Note that Weld container is started automatically // WeldInitiator can be used to perform programmatic lookup of beans assertEquals("baz", weld.select(Foo.class).get().getBaz()); // WeldInitiator can be used to fire a CDI event weld.event().select(Baz.class).fire(new Baz()); } } 9

Slide 10

Slide 10 text

TestContainers: GlassFish ● https://github.com/eclipse-ee4j/glassfish.docker/pkgs/container/glassfish or search for the Eclipse GlassFish Docker Image ● Supported platforms: amd64 and arm64 ● Id: ghcr.io/eclipse-ee4j/glassfish:7.0.18 ● We are still waiting for the merge of the PR for DockerHub

Slide 11

Slide 11 text

TestContainers: GlassFish @Testcontainers public class AsadminTest { @Container private final GenericContainer server = new GenericContainer <>(”ghcr.io/eclipse-ee4j/glassfish:7.0.18” ) .withCommand("asadmin start-domain" ).withExposedPorts (8080) .withLogConsumer (o -> System.err.print("GF: " + o.getUtf8String())); @Test void getRoot() throws Exception { URL url = URI.create("http://localhost:" + server.getMappedPort(8080) + "/").toURL(); HttpURLConnection connection = (HttpURLConnection ) url.openConnection (); String content; try { connection.setRequestMethod ("GET"); assertEquals(200, connection.getResponseCode (), "Response code" ); try (InputStream in = connection.getInputStream ()) { content = new String(in.readAllBytes(), StandardCharsets .UTF_8); } } finally { connection.disconnect(); } assertThat(content, stringContainsInOrder("Eclipse GlassFish" , "index.html", "production-quality" )); } } 11

Slide 12

Slide 12 text

TestContainers: GlassFish + Web Application @ApplicationPath("") public class SimpleApplication extends Application { @Override public Set> getClasses() { return Set.of(SimpleResource .class); } } @Path("/") public class SimpleResource { @GET public Response getResponse() { return Response.ok("Hello TestCon!" ).build(); } } 12

Slide 13

Slide 13 text

TestContainers: GlassFish + Web Application public class AsadminTest { private static final Logger LOG = System.getLogger(AsadminTest.class.getName()); private static final String APP_NAME = "application"; private static final String APP_FILENAME = APP_NAME + ".war"; @TempDir private static Path tempDir; private static Path applicationPath; private static GenericContainer server; @BeforeAll public static void init() { WebArchive webArchive = ShrinkWrap.create(WebArchive.class).addClass(SimpleResource.class) .addClass(SimpleApplication.class).addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); LOG.log(INFO, webArchive.toString(true)); applicationPath = tempDir.resolve(APP_FILENAME); webArchive.as(ZipExporter.class).exportTo(applicationPath.toFile(), true); server = new GenericContainer<>("ghcr.io/eclipse-ee4j/glassfish:7.0.18").withCommand("startserv") .withExposedPorts(8080).withLogConsumer(o -> System.err.print("GF: " + o.getUtf8String())) .withCopyFileToContainer(MountableFile.forHostPath(applicationPath), "/opt/glassfish7/glassfish/domains/domain1/autodeploy/" + APP_FILENAME) .waitingFor( Wait.forLogMessage(".*Successfully autodeployed.*", 1).withStartupTimeout(Duration.ofSeconds(10L))) ; server.start(); } 13

Slide 14

Slide 14 text

TestContainers: GlassFish + Web Application @Test void getRoot() throws Exception { String content = get(""); assertThat(content, stringContainsInOrder("Eclipse GlassFish", "index.html", "production-quality")); } @Test void getApplication() throws Exception { assertThat(get(APP_NAME), stringContainsInOrder("Hello TestCon!")); } private String get(String endpointRelativePath) throws IOException { URL url = URI.create("http://localhost:" + server.getMappedPort(8080) + "/" + endpointRelativePath).toURL(); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); String content; try { connection.setConnectTimeout(10); connection.setReadTimeout(1000); connection.setRequestMethod("GET"); assertEquals(200, connection.getResponseCode(), "Response code"); try (InputStream in = connection.getInputStream()) { content = new String(in.readAllBytes(), StandardCharsets.UTF_8); } } finally { connection.disconnect(); } return content; } 14

Slide 15

Slide 15 text

TestContainers: GlassFish + Web Application [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.920 s -- in org.omnifish.testcon.tc. AsadminTest 15

Slide 16

Slide 16 text

TestContainers: Embedded GlassFish @Testcontainers public class EmbeddedTest { @Container private final GenericContainer server = new GenericContainer <>("ghcr.io/eclipse-ee4j/glassfish:7.0.18" ) .withCommand("runembedded").withExposedPorts (8080).withLogConsumer (output -> { // FIXME: If we don't use the interactive terminal, spams STDOUT. To be fixed in 7.0.19+. if (output.getType() == OutputType.STDERR) { System.err.print("GF: " + output.getUtf8String()); } }); @Test void getRoot() throws Exception { URL url = URI.create("http://localhost:" + server.getMappedPort(8080) + "/").toURL(); HttpURLConnection connection = (HttpURLConnection ) url.openConnection (); try { connection.setRequestMethod ("GET"); assertEquals(404, connection.getResponseCode (), "Response code" ); } finally { connection.disconnect(); } } } 16

Slide 17

Slide 17 text

Fake Production Database ● When you want to reproduce something from production ● Randomized data, thin ice ● Usually a temporary solution ● Corrupted data sooner or later

Slide 18

Slide 18 text

TestContainers: Database + GlassFish ● When you plan to do awful things to your data ● Postgress container, MySql, etc. ● The database is destroyed after the test ● Similar way you can create containers for anything just as if you would build a virtual network

Slide 19

Slide 19 text

TestContainers: Fake Production Database When you want to test integration of your Java code with JPA, JTA, JDBC, and real SQL database. When you want to check compatibility with several versions of the database. Idea: 1. Implement tool able to copy consistent data from some source and use it for selective dumps. 2. Maintain such source using the application. 3. Dump those data from time to time and add them to GIT (csv, json, unl, …) 4. Use @Container and your own impl loading dump before the test. 5. Run some test sequence whenever you want.

Slide 20

Slide 20 text

GlassFish Test Utilities ● When you don’t like TestContainers ● Ensures proper start+stop of the domain/cluster/… ● Able to test whatever admin would do with the domain ● GlassFishTestEnvironment class