Unit test with database

Asked

Viewed 2,543 times

8

I have an application that uses JPA 2 with Hibernate behind and for unit testing, I use HSQLDB in memory with Junit 4.11. HSQLDB is configured in a persistence.xml file in a META-INF folder.

My question is: How do I upload the clean database at the beginning of each test without having to manually call a bunch of "DELETE FROM BLA" or similar thing?

Currently I have problems within the methods @Before, a call something like this:

EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);

The EntityManagers produced are placed in a variable ThreadLocal, to ensure that each thread does not manipulate the EntityManagers from other threads.

In the methods @After I call a entityManager.clear().

However, the test is unreliable. Sometimes saved objects disappear for no apparent reason. And often the cache of Hibernate deceives me by showing objects that are not persisted, but seem to be, and with that I end up abusing the use of entityManager.clear() by precaution in places where this should not be necessary.

Someone has a better suggestion of some strategy to develop tests using HSQLDB in memory with Junit?

  • 1

    Not to be annoying, but already being: if you are accessing a database (even if it is in memory), it is not a unit test -- it is an integration test. Having said that, I’ve had success in the past using Dbunit for these tests, running appropriate operations in the setup/tearDown methods.

  • 1

    @Lias , whether or not it is a unit test for accessing a database in memory is debatable (and has been discussed for years). One of the counterarguments is that everything should then be called an integration test, since you depend on the proper functioning of the operating system, or Java itself. In short, I would not change the technology to suit the name. I would change the name, to suit the technology. I see a lot more value in testing with real items, than introducing Dbunit just so I can call my tests "unit tests" :-)

  • @jpkrohling the suggestion to introduce the Dbunit was to solve the OP problem, the tests would still be integration tests. = ) The counterargument that "everything should be called an integration test" is invalid, because the difference between the tests is not simply what it depends on to work, but rather the objectives of the tests. Unit tests aim: 1) get quick feedback, 2) get a good design by putting yourself in the user’s shoes, and 3) reduce our fear when making changes.

  • @jpkrohling To make unit tests in the case presented, the work would probably involve creating a new test suite, using mocks/stubs for contributors, in order to isolate only the code written in the actual unit (do not test the code of libraries and frameworks, for example), and so get the desired quick feedback (type, >1s is not fast). = ) Recommended reading: http://www.javacodegeeks.com/2012/09/test-driven-traps-part-1.html

  • @Indeed, I am well aware of the definition of unit tests, but I think the definition is often taken too literally. The main point is that only one unit of your code is tested, regardless of the number of things that happen behind this code. If a line of my code calls 1,000 lines of Hibernate, then I certainly want to test my integration with Hibernate, since a version change can affect me. Same thing with JVM, or with native calls. There are cases, of course, where it is desired to test only its logic, as in complex algorithms.

  • In time, I believe that none of the mentioned items are determinant to classify OP tests as unitary or integration: with DB in memory, they give a quick feedback, encourage a good design (the tests are the first consumers of the code)and certainly reduce the fear at the time of the changes :-)

  • @I believe that everything depends on the purpose of the test. If we are testing a specific component to ensure its proper functioning, even if this component resides in a "bubble" (the bank is "fake", the entries are fake, etc.), then we are doing a unit test. If we assume that two or more components work as they should (for example, after unit testing on each of them separately) and we want to see if they interact properly with each other, then we have integration tests. Response time has nothing to do with it (although in practice we want quick feedback).

  • 1

    @jpkrohling without extending myself (even because comments are not the right place for this type of discussion), I believe that in your example the line that interfaces with Hibernate can be tested (Unit) using false inputs and outputs that correspond to what you expect from Hibernate. But the moment you are interested in how its component interacts with specific versions of Hibernate, so what you’re testing is the integration between the two systems.

  • 1

    @mgibsonbr, true, this discussion goes far (it is one of the most discussed topics in the world of testing). The fact is that the purpose of the test is not to test the integration with Hibernate, but the test will certainly fail when something in Hibernate changes. And that’s a good thing.

  • 1

    I’m sorry if I’m being repeated something that can already be understood by the previous comments, but only to reinforce, when one test can influence another (either by the sequence of execution or due to competition) then the concept of unit test is threatened and problems like this will arise. Citing the answer from @Marcoszolnowski, there should be a new bank instance for each test.

  • Response time is quite significant in the medium term, because the consequence of a slow test suite is to stop running it, and the consequence is often to stop writing tests. I’m talking here, but been there, done that -- I am the first guilty of doing the same thing, often. = ) Maybe this is really a matter of opinion according to experience.

Show 6 more comments

3 answers

5


You have a EntityManager per thread, but all share the same database. If you run the tests concurrently, what one test does, can affect the other tests.

It is necessary to create the database in memory at each test.

I will quote a tutorial. Where he creates a file Hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                         "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
 <session-factory name="">
  <property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
  <property name="hibernate.connection.url">jdbc:hsqldb:mem:testdb;shutdown=false</property>
  <property name="hibernate.connection.username">sa</property><!-- default username -->
  <property name="hibernate.connection.password"/><!-- default password -->
  <property name="hibernate.connection.pool_size">10</property>
  <property name="hibernate.connection.autocommit">true</property>
  <property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
  <property name="hibernate.hbm2ddl.auto">create-drop</property><!-- creates the tables from the entites automatically -->
  <property name="show_sql">true</property>
  <property name="dialect">org.hibernate.dialect.HSQLDialect</property>
  <mapping class="blog.hibernate.employee.Employee"/>
 </session-factory>
</hibernate-configuration>

And puts in the @BeforeClass of the test:

    @BeforeClass
    public void init() {
        config = new AnnotationConfiguration();
        config.configure(new File("hibernate.cfg.xml"));
            factory = config.buildSessionFactory();
            hibernateSession = factory.openSession();
        }

It’s a little old tutorial, but it should be very close to what you need to do in this version of Hibernate.

2

There’s a Hibernate property called hibernate.hbm2ddl.auto. When you’re in create-drop, will erase everything and recreate from scratch, ideal situation for your tests. What I usually do is have a base class that takes care of recreating the database at each test. Note that this happens with each test method, making the execution of your tests slow, but it is a price I think it is fair to pay. A good reference source is Hibernate’s own test suite. They use a different output, which is to recreate the database for each class. For this, they use some extensions of Junit, which may be too much for normal case.

Here are some examples that I use in personal projects, which may serve as inspiration:

META-INF/persistence.xml, with nothing else, except the property that erases and recreates the database.

Testwithentitymanager, base class for tests requiring a database.

Categoryservicetest, example of a class using Testwithentitymanager.

1

As already mentioned in other answers, Hibernate is able to create the database structure by adjusting the property hibernate.hbm2ddl.auto.

However, the use of this feature is unfeasible in somewhat more complex scenarios where, for example, there is a DBA in the process. There are many other issues in this regard, as we often need fine-tuning in the database: index creation, specific DBMS structures, data loads.

Solutions to this came in this year’s JPA 2.1 specification, which allow SQL scripts to be executed before or after Metadata database, or only scripts are executed. See some articles about the subject. Then it would be possible to define a script to create the necessary database structure at the beginning of each test.

Another approach to create a "Frankenstein" that mixes unit test and integration (I don’t want to get into the discussion here of what is what) is to use a test framework that allows some more adjustments, such as the Testng. This has an API very similar to Junit, but with some extra features:

  • Specify dependency between tests or test group (influences the order of execution)
  • Specify tests that may or may not be run in parallel
  • Tests parameterized with a Data Provider

(For comparisons between Junit and Testng ver these links.)

Anyway, I made a brief presentation about Testng because it allows you to create something between a unit test and an integration test through the test clusters and the dependency between them.

To illustrate, someone could create a test @Test processarContratos() that depended on the test @Test incluirCliente() (which includes a customer) and test @TestincluirContrato(Contrato c) (which is linked to a Dataprovider and includes several contracts). Methods annotated with @BeforeSuite and @AfterSuite can be used to initialize and terminate the resources needed for the methods of a Test Suit.

Browser other questions tagged

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