Chu
Chu

Reputation: 478

Transactional test method not throwing exceptions when using rollback

The test method remove is trying to remove some user with the id 147 but this id does not exist. If I enable Rollback(false) I get an exception (the expected behavior) but without it, the test pass without problems. So I have two questions:

  1. Why does the test fail only when disabling rollback?
  2. Is it possible to get the exception enabling rollback?

UserDao is inheriting from a generic DAO class which has the @Transactional (default options) and @Repository (with the bean name) annotations at class level.

Here is the exception I get when disabling rollback.

I am using Spring Framework 4.3.9, Hibernate 5.2.10 and JUnit 4.12

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@Transactional
@ContextConfiguration({
        "classpath:myapp-config-test.xml",
        "classpath:hib-test.xml"})
public class UserControllerTest {

    private MockMvc mockMvc;
    private MvcResult mvcResult;
    private final String basePath = "/users/";

    @Autowired
    private UserDao userDao;

    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.standaloneSetup(new UserController(userDao)).build();
    }

    @Test
    //@Rollback(false)
    public void remove() throws Exception {
        mockMvc.perform(delete(basePath + "147")).andExpect(status().isOk());
    }
}

Upvotes: 0

Views: 590

Answers (1)

JB Nizet
JB Nizet

Reputation: 691625

Read the stack trace, from bottom to top:

Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
    at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:67)
    at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:54)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:46)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3315)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3552)
    at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:99)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:589)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1435)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:491)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3201)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2411)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:467)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:146)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:220)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68)
    at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:582)
    ... 24 more

You can see that the exception happens when the transaction manager commits the transaction:

at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:582)

Committing the transaction causes the Hibernate session to flush the changes that have been made, and kept in memory:

at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3201)

And indeed, the flush causes the deletion to actually be executed on the database:

at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:99)

And since the deletion doesn't delete anything, the exception is thrown:

org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

So, in order to actually execute the deletion, and to throw the expected exception, you need to flush.

But you shouldn't test the behavior of a DAO (and even less, the behavior of Hibernate, which is already tested by Hibernate itself), in the unit test of an MVC controller. Instead, you should mock the dependencies of the controller (i.e. the DAO) when unit-testing the controller. And have another test for the DAO.

Upvotes: 2

Related Questions