Unit test/ Mock of a JPA query

Asked

Viewed 1,776 times

3

Hello, how do I generate a unit/mock test of a JPA/Criteria query ?

What can I guarantee by mocking my objects? Table structure? What should I mock?

The consultation is very simple, only one select * from in the table/class Teste

In case my criteria have conditions in Where, it makes a difference to the mock?

In the precise case of uniqueness test, it cannot be integration test.

Below is the code I want to test, it is a generic method that returns all the records of a given table/entity:

public List<T> findAll(Class<T> entityClass) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> cq = cb.createQuery(entityClass);
        Root<T> root = cq.from(entityClass);
        cq.select(root);

        TypedQuery<T> query = entityManager.createQuery(cq);
        return query.getResultList();
}

I want to test that given a certain class, I can return all records from the table.

  • 1

    But what do you want to test? When we write an automated test, we try to know for example if "given a certain input the code produces a certain output" or if "given certain conditions the system behaves in such a way". I’ve also written tests to make sure string queries don’t break after typing. So depending on the goal, you have a few different options - such as not testing :-) What you expect to know from the result your test?

  • I improved the question :) I want to test that given a certain class, I can return all records from the table.

1 answer

4


To test whether the method findAll fulfills its role of returning all entities of a given type, the best option is to fire this query against a real database.

I explain: you are testing a very close implementation of the database. Between your code and the database, there is only the JPA framework (Eclipselink, Hibernate, whatever). Since it makes no sense to test these frameworks or the database, you have to let these components perform their work to see if your code can, from the interaction with them, deliver the expected result.

In some projects, this method of yours would even be tested because the cost of testing it is great (large dependency on other components of architecture) and the value in testing it is small (the code itself does very little, there are no important rules there to be validated). In these projects, instead of writing a test for this method, this method would be mockery, that is, it would be replaced by something else when testing another code that depends on it.

But there are other projects where the rule is that 100% of the code is covered by tests ("100% coverage"); and there is still the possibility that you want to write this type of method using TDD (where you would first write the test and then the method findAll to pass the test). In this case, how to do?

You can use a Fake instead of using a Mock

Although it is natural to call everything a "mock", perhaps a differentiation between these two types of stuntman (doubles) within your specific case:

In this another answer you have a list of other types of stuntmen.

Mock

Mock validates tested code interactions with the component that mock is replacing.

In your example, a mock would replace the Entitymanager and the other objects it delivers, and would validate whether the methods were called getCriteriaBuilder, createQuery, getResultList, etc..

Note that this does not apply because you may want to replace all this code you have today with a single line, for example:

public List<T> findAll(Class<T> entityClass) {
    return entityManager.createQuery(String.format("select t from %s t", 
        entityClass.getSimpleName()), entityClass).getResultList();
}

If you were using the mock concept, the test of this method would fail although the method still fulfills its role. Also, you will want to test other methods like this, only more complex (using filter, for example).

Generally speaking, although we call everything "mock", rarely a real mock helps much. It is complex and the demand for it often indicates a design problem or an obtuse goal for the test being written.

Fake

Fake is a type of stunt that has a complete implementation of the component that is replacing itself.

It is used because it is more suitable for testing than the actual component (for example, faster) but cannot be used in production because of other requirements (customer prefers to use another component or this one we use as fake does not support the production load, for example).

You would use a fake here to replace the database. The databases I have successfully used for this are the H2 and the HSQLDB.

The advantage of these databases over an DBMS plus Nterprise (as Oracle and MS SQL Server) is that they can be used fully in memory and within the same testing process, eliminating the need for inter-process communication or network access and eliminating disk access, which makes testing much faster.

The testing process using a database in memory as a stuntman fake is as follows:

  • The project has an alternative bank access configuration, which is used only during automated tests. This setting naturally points to the database in memory.

  • At the start of the tests, the database is automatically created in memory from the entity settings of your project.

  • At the beginning of each test, i.e., in the preparation for the test, you enter in the database the entities that will be used by that particular test, and delete them at the end of the test or simply rollback the transaction.

Conslusion

It is necessary to be clear the objective of the test that is being implemented.

There are several types of stuntmen to be used in automated testing, and mock most of the time is the least indicated.

A stuntman of the type fake, on the other hand, it can be very useful for testing database access implementations or for testing services (which traverse multiple layers of architecture).

What you can test with better performance using a database fake is:

  • Whether queries continue to work after changing entity and attribute names or after changing table and column names in the database.

  • If your data access implementation (repositories or Daos) continues to work after changing a query strategy, such as the replacement I showed for your original code.

  • And you can also run service tests, which go through various layers of architecture, including the data bank.

  • 1

    Excellent answer, clarified several questions. The part where you mention that my code to be tested should be mocked by another test helped me a lot to understand what I should test.

  • To implement a Fake database, the Dbunit - http://dbunit.sourceforge.net/ could be used?

  • @Brunogasparotto I don’t know this tool. Giving a quick read seems cool. In fact having to enter the records before each test is laborious so we usually build libraries just to streamline and simplify it. Being able to carry a ready-made base before each test seems to make it easier. One has to worry though with the abstraction of the test - if I insert the record in the test itself, it is obvious to understand why the test expects a certain result, but if the records are already there the test is coupled to that set of "hidden records".

  • 1

    @Brunogasparotto also comes the temptation to use the same set of records for various tests, then when a test comes to need a slightly different set it can modify the set and harm another test or it can create a very similar new set until we have too many sets and maybe we don’t know which ones are useful and which aren’t anymore. Finally, we must study the tool and analyze the pros and cons for the project that is in hand.

  • @Caffé You enter the records at each test. The entire database is populated before the tests run, and undergoes a rollback or is simply destroyed after the tests. You can have a set of records for multiple tests, or for a specific test, that is, you can have as many sets as you want and use where and when you want.

  • @Caffé From what I understood of your answer, the tool fits the proposed technique, provided it is used carefully and without coupling between the tests. Correct?

  • 1

    @Brunogasparotto Yes, that’s correct. I don’t know if in your penultimate comment you refer to my answer, so just to clarify: I input before each test the set of records required for that test, and delete them when the test ends or rollback the transaction. When using a single instance of DB (fake or not) for all tests, if you want to run in parallel you have to be careful about the interference that the records of one test may have in another; one way to solve this is to keep each test in its own transaction. The tool you pointed out seems to take good care of this aspect.

Show 2 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.