1

I'm a little bit new to hibernate, so I started with simple things.

According to F.I.R.S.T test principles, unit tests must be I - isolated. I'm trying to apply it to integration tests for repository layer (Hibernate\JPA) using @Transactional annotation:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RepositoryConfig.class)
@Transactional
public class EmployeeRepositoryTest extends AbstractRepositoryTest {    
    @Autowired
    private IEmployeeRepository employeeRepository;    
    @Test
    public void saveTest() {
        Employee expectedEmployee = buildEmployee(1, "Parker");
        employeeRepository.save(expectedEmployee);
        Employee actualEmployee = employeeRepository.findById(1);
        assertEquals(expectedEmployee, actualEmployee);
    }
    private Employee buildEmployee(long id, String name) {
        Employee employee = new Employee();
        employee.setId(id);
        employee.setName(name);
        return employee;
    }
}

However, as far as two methods are performed within a transaction, hibernate does not actually perform them (as I understand it) - at least there's no line with insert in logs.

If I run data insertion by adding a script to embeded datasourse like:

INSERT INTO employee (employee_id, employee_name) VALUES (1, 'name');

and try to save employee with the same id but new name, the test will success. And that's the most confusing thing for me.

I saw a solution with autowiring EntityManager and calling it's flush() method. But I don't like it, since I try to write tests without being tied to Hibernate\JPA.

I also tried different flushMode, but it didn't help either.

Q1: Is there a way to make Hibernate run queries right after repository's method is called?

Q2: Is it a good practice to call EntityManager#flush in save/update/delete repository methods explicitly?

My Employee:

@Entity
@Table(name = "employee")
public class Employee {    
    @Id
    @Column(name = "employee_id")
    private long id;        
    @Column(name = "employee_name")
    private String name;
    // the rest required things (constructor, getters/setters and etc)
}

and RepositoryConfig:

@Configuration
@EnableTransactionManagement
@ComponentScan("org.my.package")
public class RepositoryConfig {    
    @Bean
    public DataSource getDataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
    }    
    @Bean
    public JpaTransactionManager transactionManager() {
        return new JpaTransactionManager();
    }    
    @Bean
    @Autowired
    public HibernateTemplate getHibernateTemplate(SessionFactory sessionFactory) {
        return new HibernateTemplate(sessionFactory);
    }    
    @Bean
    public LocalSessionFactoryBean getSessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(getDataSource());
        sessionFactory.setPackagesToScan("org.my.package.model");
        sessionFactory.setHibernateProperties(getHibernateProperties());
        return sessionFactory;
    }    
    private Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", "H2Dialect");
        properties.put("hibernate.show_sql", true);
        return properties;
    }
}
Dmitry Dyokin
  • 139
  • 11

1 Answers1

1

You have no option but to interact with the entity manager to get these tests working as you expect - not to trigger a flush (as that can be done by calling saveAndFlush(..) method on your repository rather than just save(...)) but to clear the first level cache:

https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html#saveAndFlush-S-

@Test
public void saveTest() {
    Employee expectedEmployee = buildEmployee(1, "Parker");

    //call save and flush for immediate flush.
    employeeRepository.saveAndFlush(expectedEmployee);

    //now you will need to clear the persistence context to actually
    //trigger a load from the database as employee with id 1 is already
    //in the persistence context. 

    //without the below you will not see a db select
    entityManager.clear();

    Employee actualEmployee = employeeRepository.findById(1);
    assertEquals(expectedEmployee, actualEmployee);
}

An alternative to clearing the persistence context is to fall back to using raw JDBC to read the updated row(s).

But I don't like it, since I try to write tests without being tied to Hibernate\JPA. You are testing a persistence mechanism implemented in Hibernate\JPA and your repository is just an abstraction that is allowing you to avoid direct calls to it so this seems a slightly ridiculous statement.

Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • by writing tests without being tied to Hibernate\JPA I mean that I don't want to have in my tests anything belonging to Hibernate\JPA specific things. So If hypothetically I migrate to iBatis, for example, I won't need to change tests. – Dmitry Dyokin Dec 05 '18 at 12:48
  • You are testing a **JPA** repository. If you want to test an iBatis repository then you'd write a new test. The best you can do in you current test is call `saveAndFlush()` and then - as noted in the answer fall back to using raw JDBC to read the updates which would prevent you having to call `clear()` on the em. – Alan Hay Dec 05 '18 at 12:59
  • i'm afraid i didn't get your point. Just to clarify, i'm writing integration tests (using H2), from test-/behavior-driven point of view it doesn't matter what engine is running behind the scene. So, does the fact of changing framework change the expected result of my interface methods? – Dmitry Dyokin Dec 05 '18 at 13:57
  • You don't test Interfaces. You test implementations. Right now the unit under test is a JPA repository implementation - employeeRepository - so test that. In future you might have an iBatis one so test that when you have it. Maybe you need to clarify your question....... – Alan Hay Dec 05 '18 at 14:22
  • Yes, you're right - i test interfaces' implementations. But does it change the logic? Maybe, it's not clear for me and you could explain the next thing. I'm trying to test the repository logic in conjunction with in-memory DB. I assume it's integration tests then. Why should I care about the underlying engine? – Dmitry Dyokin Dec 05 '18 at 15:05
  • I feel the answer provider addresses the technical aspects of your question i.e. how to force write/read to/from database to make your test work as expected. If so, you should accept the answer and raise a new question if you have any further concerns. – Alan Hay Dec 06 '18 at 16:08