testcontainers / testcontainers-java

Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
https://testcontainers.org
MIT License
8.05k stars 1.66k forks source link

[Feature]: Application Server Module #7098

Open KyleAure opened 1 year ago

KyleAure commented 1 year ago

Module

New Module

Problem

As a Java test framework, it would be great for the Testcontainers project to expand into the Jakarta EE space by offering an application platform module with application containers.

This would allow application developers the ability to perform functional tests on their applications. This would allow platform developers the ability to perform integration tests on their platform.

Solution

The solution I am proposing would be to have a new module named application-platform

With an abstract container type of ApplicationContainer

Implementations of this container would come from vendors, for example:

[WIP] An ApplicationContainer would contain:

abstract ApplicationContainer: 
  optional configurationFile
  optional applicationPackages (pre-compiled from src/ for functional testing)
  optional applicationArchives   (created from test/ at runtime for integration testing)
  required appContextRoot

Benefit

The benefit of having this feature would be to streamline the process of performing functional and integration testing on Jakarta EE platforms.

Alternatives

The direct alternative would be to use a generic container with a bespoke configuration.

The comprehensive alternative would be to use Microshed Testing which isn't being maintained.

The other alternative is Arquillian which has its own concept of "containers" which doesn't use docker as the underlying technology.

Would you like to help contributing this feature?

Yes

eddumelendez commented 1 year ago

Thanks for raising the issue, Kyle! Can you elaborate more about applicationPackages and applicationArchives, please?

@kiview and I were experimenting a little bit yesterday. There are different ways to build the artifact:

Maven Invoker

static InvocationResult buildWar() {
    try {
        DefaultInvocationRequest defaultInvocationRequest = new DefaultInvocationRequest();
        defaultInvocationRequest.setMavenHome(new File(System.getenv("MAVEN_HOME")));
        defaultInvocationRequest.setJavaHome(new File(System.getenv("JAVA_HOME")));
        defaultInvocationRequest.setPomFile(Paths.get("pom.xml").toFile());
        defaultInvocationRequest.setGoals(Collections.singletonList("war:war"));
        Invoker appInvoker = new DefaultInvoker();
        return appInvoker.execute(defaultInvocationRequest);
    } catch (MavenInvocationException e) {
        throw new RuntimeException("Could not build war", e);
    }
}

static GenericContainer<?> jakartaApp = new GenericContainer<>("payara/micro:5.2021.9-jdk11")
            .withExposedPorts(8080)
            .withCopyFileToContainer(MountableFile.forHostPath("target/jakarta-tc.war"), "/opt/payara/deployments/jakarta-tc.war")
            .waitingFor(Wait.forLogMessage(".* Payara Micro .* ready in .*\\s", 1));

ShrinkWrap

File[] deps = Maven.resolver().loadPomFromFile("pom.xml").importCompileAndRuntimeDependencies().resolve().withTransitivity().asFile();

WebArchive archive = ShrinkWrap.create(WebArchive.class,"jakarta-tc.war")
    .addClass(App.class)
    .addPackages(true,
        "dev.wittek.tc.jakarta.book",
        "dev.wittek.tc.jakarta.dice",
        "dev.wittek.tc.jakarta.time",
        "dev.wittek.tc.jakarta.turing")
    .addAsResource("META-INF/persistence.xml")
    .addAsResource("META-INF/init.sql")
    .addAsResource("turing.csv")
    .addAsLibraries(deps);
archive.as(ZipExporter.class).exportTo(Paths.get("target/jakarta-tc.war").toFile(), true);

IDK if there is a better way to do this with ShrinkWrap, that's how I remember I did in a project some years ago. Any suggestion is welcome.

We were thinking about adding the ApplicationServer support such as PayaraMicroContainer, TomcatContainer and so on, with proper documentation about how can be used with Maven invoker, Gradle TestKit and ShrinkWrap. Thoughts?

KyleAure commented 1 year ago

If I think about the possible projects an ApplicationContainer could be used in I can think of two distinct use cases:

  1. I am developing an application, and want to deploy my application and run tests on an ApplicationContainer (integration testing)
  2. I am developing a library that uses Enterprise Java and want to deploy many applications and run tests on those applications. (functional testing)

Integration Testing

When performing these types of tests I have the following project structure:

target || build
  - libs/
    - app.war 
src/
  - main/
    - java/
    - resources/
  - test/
    - java/
    - resources/
pom.xml || build.gradle

I can configure maven/gradle to build the .war prior to running my tests. Thus an ApplicationContainer would look something like this:

@Container
static final ApplicationContainer<?> liberty = new LibertyContainer<>("open-liberty:23.0.0.4-full-java11-openj9")
        .withApplicationPackages(MountableFile.forHostPath("build/libs/testapp.war"))
        .waitingFor(Wait.forLogMessage(".*CWWKZ0001I: Application .* started in .* seconds.*", 1))
        .withLogConsumer(new LogConsumer(Test.class, "liberty"));

Functional Testing

When performing these types of tests I have the following project structure:

target || build
  - libs
    - library.jar 
src/
  - main/
    - java/
    - resources/
  - test/
    - java/
      - webApp1/
      - webApp2/
      - test.java
    - resources/
pom.xml || build.gradle

In this case, the applications I want to deploy are within my test directory itself, so I'm not able to rely on maven/gradle to have built and packaged them already.
Therefore, I need a runtime method to achieve this (Shrinkwrap being an existing library that allows me to do this).

Thus an ApplicationContainer would look something like this:

@Container
static final ApplicationContainer<?> liberty = new LibertyContainer<>("open-liberty:23.0.0.4-full-java11-openj9")
        .withApplicationArchvies(getApp1(), getApp2())
        .waitingFor(Wait.forLogMessage(".*CWWKZ0001I: Application .* started in .* seconds.*", 2))
        .withLogConsumer(new LogConsumer(Test.class, "liberty"));

static WebArchive getApp1() {
  return ShrinkWrap.create(WebArchive.class, "app1.war")
    .addPackage("webApp1");
}

static WebArchive getApp2() {
  return ShrinkWrap.create(WebArchive.class, "app2.war")
    .addPackage("webApp1");
}

Naming

Obviously withApplicationArchvies and withApplicationPackages are just what I came up with on the spot. Maybe we don't need to differentiate between the two and just use: withApplicationArchvies and overload the method.

eddumelendez commented 1 year ago

Maybe we don't need to differentiate between the two and just use: withApplicationArchvies and overload the method.

I like it! looking forward to it 🤗

Also, I saw that Glasshfish is going to be available at DockerHub. We should consider it when it's back again. WDYT?

KyleAure commented 1 year ago

@eddumelendez @kiview I created a draft PR to put together most of what we have talked about in this issue.