Kohei Nozaki's blog 

Jukito integration with JPA and guice-persist


Posted on Sunday Jul 31, 2016 at 11:28AM in Technology


A DI container such as Guice helps you to assemble loosely-coupled classes that easy to write unit tests.

But integration tests, that are necessary thing as well as unit tests, it’s a different story from writing unit tests. writing integration tests involve many cumbersome initialization code of frameworks such as DI container, JPA provider or an application server. they tend to spread across testcases and make testcases messy.

If you use Guice as DI container, Jukito helps you to write integration tests. its official documentation covers some simple usecases, but not mentioned about integration with guice-persist. so in this entry I’ll introduce you how to integrate and begin writing clean testcases without messy boilarplate code with them.

Create an application to be tested

Consider we have a simple JPA entity and a service class which uses it.

An entity class named Employee:

@Entity
public class Employee implements Serializable {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    public Employee(final String name) {
        this.name = name;
    }
...

The service class to be tested named EmployeeService:

public class EmployeeService {

    @Inject
    private EntityManager em;

    @Transactional
    public Long save(final String name) {
        final Employee employee = new Employee(name);
        em.persist(employee);
        em.flush();
        return employee.getId();
    }

    public List<String> findAllNames() {
        return em.createQuery("SELECT e.name FROM Employee e ORDER BY e.name", String.class).getResultList();
    }
}

Create a module for testing

Next, we will create a module for testing (BTW I recommend you to use Module overriding for creating one for testing, based on production one). this would be something like:

// Taken from https://gist.github.com/JWGmeligMeyling/785e459c4cbaab606ed8 , thanks!
public class DatabaseModule extends AbstractModule {

    @Override
    protected void configure() {
        install(new JpaPersistModule("myPU"));
        bind(JPAInitializer.class).asEagerSingleton();
    }

    @Singleton
    private static class JPAInitializer {
        @Inject
        public JPAInitializer(final PersistService service) {
            service.start();
        }
    }
}

Create JpaJukitoRunner

Then, create a special TestRunner which extends JukitoRunner as follows:

public class JpaJukitoRunner extends JukitoRunner {

    public JpaJukitoRunner(final Class<?> klass) throws InitializationError, InvocationTargetException, InstantiationException, IllegalAccessException {
        super(klass);
    }

    public JpaJukitoRunner(final Class<?> klass, final Injector injector) throws InitializationError, InvocationTargetException, InstantiationException, IllegalAccessException {
        super(klass, injector);
    }

    private UnitOfWork unitOfWork;

    @Override
    protected Object createTest() throws Exception {
        this.unitOfWork = getInjector().getInstance(UnitOfWork.class);
        this.unitOfWork.begin();
        return super.createTest();
    }

    @Override
    public void run(final RunNotifier notifier) {
        notifier.addListener(new RunListener() {

            @Override
            public void testFinished(final Description description) throws Exception {
                // this ensures every tests use distinct entity manager instances
                unitOfWork.end();
            }
        });
        super.run(notifier);
    }
}

This ensures every tests use distinct EntityManager instances for each execution. without this, only one EntityManager instance will be used for all of executions of test cases because it is stored in a ThreadLocal and JUnit uses only one Thread by default for all test executions.

That means that many entities will be kept managed during an execution. consider if you have thousands of test classes in your project - entities will be shared across all of test execution. it will make your EntityManager fat, also your tests may get affected by 1st level cache which got dirty by other test executions.

Create a testcase

Finally, you can write a testcase as follows:

@RunWith(JpaJukitoRunner.class)
@UseModules(DatabaseModule.class)
public class EmployeeServiceTest {

    @Inject
    private EmployeeService sut;
    @Inject
    private EntityManager em;

    @Before
    @Transactional
    public void setUp() throws Exception {
        em.createQuery("DELETE FROM Employee").executeUpdate();
    }

    @Test
    public void saveShouldPersistJohnDoe() throws Exception {
        final String name = "John Doe";

        final long id = sut.save(name);

        final Employee employee = em.find(Employee.class, id);
        assertThat(employee.getName(), is(name));
    }

    @Test
    public void findAllNamesShouldReturnExpectedResult() throws Exception {
        sut.save("Jane Doe");
        sut.save("John Doe");

        final List<String> result = sut.findAllNames();

        assertThat(result.size(), is(2));
    }
}

You can see that there are no any cumbersome initialization code of JPA or database stuff. and note that you can use declarative transaction management by @Transactional, for both the sut class and populating test data into your database.

Cleaning 1st level cache

I have mentioned about bad effect of 1st level cache of EntityManager earlier in this entry, so you may consider that you want to clean 1st level cache. you can do it with em.clear(), or also something like following:

@RunWith(JpaJukitoRunner.class)
@UseModules(DatabaseModule.class)
public class EmployeeServiceTestManagesUOW {

    @Inject
    private Provider<EmployeeService> sut;
    @Inject
    private Provider<EntityManager> em;
    @Inject
    private UnitOfWork unitOfWork;

    @Before
    @Transactional
    public void setUp() throws Exception {
        em.get().createQuery("DELETE FROM Employee").executeUpdate();
    }

    @Test
    public void saveShouldPersistJohnDoe() throws Exception {
        final String name = "John Doe";
        newEntityManager();

        final long id = sut.get().save(name);

        newEntityManager();
        final Employee employee = em.get().find(Employee.class, id);
        assertThat(employee.getName(), is(name));
    }

    private void newEntityManager() {
        unitOfWork.end();
        unitOfWork.begin();
    }
}

In this test, there are three distinct EntityManagers involved in a test execution. the one in setUp(), another one in invocation of sut#save() and finally one for em#find() which is used for assertion.

But in my opinion, this is an overkill and using one shared EntityManager for one test execution would be sufficient. if it didn’t work well, something may wrong with your usage of JPA.

Conclusion

We have seen how testcases which involve JPA and guice-persist could be written cleanly with Jukito. it enables us to eliminate cumbersome boilarplate code for managing and invoking Injector, JPA initialization and manual transaction management. now our testcases look pretty clean.

Executable testcases and the example project can be obtained from my GitHub repository.

References



Comments:

失礼いたします.記事を読んでいて疑問が浮かんだので質問させてくださいませ.

EmployeeServiceTestクラスから Entitymanager を取り除き,さらに
@Test
@Transactional
public void findAllNamesShouldReturnExpectedResult() ...
とすることは可能でしょうか?

まとめると,
ビジネスロジックからEntityManagerを切り離す AND ビジネスロジックでトランザクション処理を行うことは可能でしょうか?

Posted by chankane on August 19, 2018 at 02:28 PM JST #


Leave a Comment

HTML Syntax: NOT allowed