16

Testing Jakarta EE applications with CDI-test - Mastertheboss

 2 years ago
source link: http://www.mastertheboss.com/java-ee/jakarta-ee/testing-jakarta-ee-applications-with-cdi-test/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

This article introduces the Testing framework CDI-Test which you can use to test JakartaEE and Microprofile applications at scale.

CDI Test: why you need it

First of all: We all love developing in the Java EE ecosystem. Or at least we do since version 5…

Or maybe we don’t but our employers do. Partly because everything is well documented, stable and supported for many years.

And we have the choice because JEE and now Jakarta EE are open standards and we have the choice between multiple products and open source projects.

But this comes with a price: Java EE always cared in no way about how you should unit-test your software: There are no hooks, not to mention any standard framework for component testing.

Testing was really hard to do with the first versions of EJB. Today we’re probably mostly using cdi components. This makes it easier as all cdi container frameworks allow us to create a cdi container from a unit test. At least with Weld this is not really difficult.

A common option for testing Enterprise applications is Arquillian which is however more targeted at integration test and not to run unit tests at scale..

The Testing approach

When creating JUnit tests that require a cdi container there are basically two approaches:

  • Start a new cdi container for each test class or test case.
  • Start only one cdi container for all of the tests.

The first approach has the advantage of allowing for different set ups for each case. But it also has a huge drawback: Booting a cdi container takes its time for large projects.

So let’s continue to the second way:

  • Booting the cdi container once we save lots of time.
  • If we can start and stop contexts per test case the lifecycle of the components to test shouldn’t be a problem.
  • If we are able to switch mocks and cdi alternatives between tests we have the flexibility we need.

As it turns out the first two points are not a problem. For example, Weld (and DeltaSpike for the other implementations) allow us to stop and start any context at any time we like.

So the remaining issue is how to get mocks and alternatives into tests and cdi components without setting up the container differently. The answer is, at least in theory, quite simple: You create a portable extension . That takes care of inserting proxies into the wiring of the components that you need to test. Then we only need to switch the behavior of these proxies in different test cases depending on the intended outcome.

This actually is exactly what I did in cdi-test so the “wiring” of the components will look like this when running the test:

jakarta ee test

Meeting JUnit5

With the transition to JUnit 5 the test class itself can’t be a cdi component any more because JUnit performs the initialization using standard Java constructors.

To provide injection into the test class for each test a second instance of the test class is created as a cdi component and the field annotated with @Inject are copied into the actual test class. This approach allows for easy injection and also supports beans created by producer methods that check for InjectionPoints:

test jakarta ee

Let’s see it in action!

You can find the source code together with integration-tests at github cdi-test. I also put together a pure sample project cdi-test-demo which I’m going to use for the examples in this article. You should check it out to run the following examples and you can also use it as a short reference on how to setup your maven project. There is also the full documentation if you want to dig deeper.

The sample project consists of a “classic” REST application that can handle resources of type Book. BookResource defines the JAX-RS code to expose this via REST and the BookService takes care of the persistence part.

The application is fully functional and you can just deploy it to a current WildFly application server. However you need to use the Jakarta EE Preview version (Check this article for more details:How to run Jakarta EE 9 on WildFly )

Let’s look at a simple test case which uses the mock features of cdi-test:

@ExtendWith(CdiTestJunitExtension.class)
@ExtendWith(MockitoExtension.class)
class BookResourceMockedServiceTest extends BookBaseTest {
@Inject
private BookResource bookResource;
@Mock
private BookService bookService;
@Test
void storeBook() {
bookResource.addBook(firstBook);
verify(bookService).saveBook(firstBook);
verifyNoMoreInteractions(bookService);
@ExtendWith(CdiTestJunitExtension.class)
@ExtendWith(MockitoExtension.class)
class BookResourceMockedServiceTest extends BookBaseTest {

    @Inject
    private BookResource bookResource;

    @Mock
    private BookService bookService;

    @Test
    void storeBook() {
        bookResource.addBook(firstBook);
        verify(bookService).saveBook(firstBook);
        verifyNoMoreInteractions(bookService);
    }
}

In this example we’re injecting the BookResource REST service into our test class. As BookResource uses BookServiceinternally and we don’t want to test that class here, we inject a Mockito mock for that service. cdi-test automatically handles and detects such mocks when you define them in your test case. This way we can use the usual Mockito mechanisms to test the behavior of the component BookResource.

Integrating with JPA

But maybe we’d want to go for testing the whole thing and actually store the book to a database using the real implementation of BookService:

@Stateless
public class BookService {
@PersistenceContext
private EntityManager entityManager;
public List<Book> getAllBooks() {
return entityManager.createNamedQuery("allBooks", Book.class).getResultList();
public Book getBookById(long id) {
return entityManager.find(Book.class, id);
public long saveBook(Book book) {
entityManager.persist(book);
return book.getId();
@Stateless
public class BookService {
    @PersistenceContext
    private EntityManager entityManager;

    public List<Book> getAllBooks() {
        return entityManager.createNamedQuery("allBooks", Book.class).getResultList();
    }

    public Book getBookById(long id) {
        return entityManager.find(Book.class, id);
    }

    public long saveBook(Book book) {
        entityManager.persist(book);
        return book.getId();
    }
}

As you see we need to inject an EntityManager into an EJB here. By using the cdi-test-jee extension stateless beans are automatically mocked as @RequestScoped CDI components in the tests.

The one thing you need to do is to provide a factory for the EntityManager (and the Factory if you use it) which should be easy depending on the test database you want to use. For the example I use h2 and configured it accordingly in the persistence.xml. EntityManagerResourcesProvider is a custom component provided by cdi-test-jee:

@Produces
@GlobalTestImplementation
@RequestScoped
protected EntityManager provideTestEntityManager() {
return entityManagerResourcesProvider.resolveEntityManager("test-persistence");
    @Produces
    @GlobalTestImplementation
    @RequestScoped
    protected EntityManager provideTestEntityManager() {
        return entityManagerResourcesProvider.resolveEntityManager("test-persistence");
    }

So it’s important to note that all of these tests are running inside a single test suite. Which means we’re really fast! As an example run the ManyTestsBookServiceTest test which is a TestFactory and creates hundreds of tests (using JPA and the database) and runs them in just some seconds.

Using MicroProfile Config

As we want to create simple and modern applications we’re probably going to use Jakarta MicroProfile and its configuration project. Luckily cdi-test comes with the cdi-test-microprofile extension which allows to override and inject specific test configuration on a per test class basis.

As a trivial example let’s look at the ConnectionTestResource class in the sample project:

@Path("/ping")
@ApplicationScoped
public class ConnectionTestResource {
@Inject
@ConfigProperty(name = "cdi-test-demo.pong")
private String pong;
@Path("/")
public String ping() {
return pong;
@Path("/ping")
@ApplicationScoped
public class ConnectionTestResource {
    @Inject
    @ConfigProperty(name = "cdi-test-demo.pong")
    private String pong;

    @Path("/")
    @GET
    public String ping() {
        return pong;
    }
}

This JAX-RS service will simply return a fixed answer for the resource http://localhost:8080/cdi-test-demo/ping. To make things more interesting the answer string can be configure using the @ConfigProperty cdi-test-demo.pong. For the “live” deployment this is configured in the default config file microprofile-config.properties to the hello, world! (what else?).

In the test case ConnectionTestResourceTest we want to be able to test the behavior for different values of the @ConfigProperty.

This can be accomplished by redefining the value using the cdi-test-microprofile annotation @ConfigPropertyValue. In the example the first test will use the defaults from the config file while the second will override the configured value with the value from the test annotation:

@ExtendWith(CdiTestJunitExtension.class)
class ConnectionTestResourceTest {
@Inject
private ConnectionTestResource connectionTestResource;
@Test
void testDefaultAnswer() {
assertEquals("hello, world!", connectionTestResource.ping());
@Test
@ConfigPropertyValue(name = "cdi-test-demo.pong", value = "hello, test!")
void testOverriddenAnswer() {
assertEquals("hello, test!", connectionTestResource.ping());
@ExtendWith(CdiTestJunitExtension.class)
class ConnectionTestResourceTest {
    @Inject
    private ConnectionTestResource connectionTestResource;

    @Test
    void testDefaultAnswer() {
        assertEquals("hello, world!", connectionTestResource.ping());
    }

    @Test
    @ConfigPropertyValue(name = "cdi-test-demo.pong", value = "hello, test!")
    void testOverriddenAnswer() {
        assertEquals("hello, test!", connectionTestResource.ping());
    }
}

Caveats with MP config

The last example has one possible problem: The Bean that injects must have Normal Scope. Only then will it be proxied in a way that the config values can be redefined for each test. At the moment there is an open issue to handle this issue and possibly fix it in the future (see Issue on GitHub).

On the other hand you should always ask yourself if the @Dependent scope actually makes sense for a component like a service in the example above.

Final words

We’re using cdi-test in multiple projects. This, we are able to run tests automatically on a Jenkins server and locally. Altogether we’ve about 4000 singe test cases based on this extension. In our case the advantages are:

  • Although the initial boot takes some time because projects are large, running each test is fast even when JPA is integrated (Also try the test case ManyTestsBookServiceTest).
  • New tests are generally easy to create. Mocking and Test alternatives semantics are quite easy to understand and use.
  • Versions versions 8 and 9 of Jakarta EE are both supported. So you can use cdi-test to migrate the new Jakarta EE 9 namespace.

Although the versions 4 of cdi-test are still “Release Candidate” they are testing ready. I’m actually only waiting for the final release of SmallRye Config which I’m using internally.

So can you test everything using cdi-test? As said, this is not the goal of this framework. For example, the target of this framework is not integration testing therefore it will not support things like:

  • Running actual JAX-RS services for integration testing.
  • Providing full transaction cdi event integration testing.

Since we package all of our applications into containers today, you can easily run these integration tests on docker which also makes more sense since

Author and Owner of the CDI Project: Gunnar Hilling


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK