6

Perhaps, I am doing something wrong, but I can't find a good way out for the following situation.

I would like to unit test a service that uses Spring Batch underneath to execute jobs. The jobs are executed via pre-configured AsyncTaskExecutor in separate threads. In my unit test I would like to:

  1. Create few domain objects and persist them via DAO
  2. Invoke the service method to launch the job
  3. Wait until the job is completed
  4. Use DAO to retrieve domain objects and check their state

Obviously, all above should be executed within one transaction, but unfortunately, transactions are not propagated to new threads (I understand the rationale behind this).

Ideas that came to my mind:

  • Commit the transaction#1 after step (1). Is not good, as the DB state should be rolled back after the unit test.
  • Use Isolation.READ_UNCOMMITTED in job configuration. But this requires two different configurations for test and for production.
dma_k
  • 10,431
  • 16
  • 76
  • 128
  • I know that some people don't agree with this, but running a test that rolls back after running is not a very good practice. Specially if you're using an ORM, as it won't issue any insert / update command until you either do a select or flush the session. – Augusto Mar 08 '11 at 12:27
  • 1
    @Augusto: I find the rollback ability nice, as tests do not collide with each other. Otherwise one need to have one `setUp()` and exactly one `test()` per Unit Test class. One can use the compensating transaction (e.g. `delete form mytable`) but I don't see how it fits the Spring philosophy. – dma_k Mar 09 '11 at 15:14
  • Again, I don't think everyone agrees on what I suggest. And you need to be sure that you're actually testing something, otherwise it's like having a test without assertions. – Augusto Mar 09 '11 at 15:35

3 Answers3

2

I think the simplest solution would be configure the JobLauncher with a SyncTaskExecutor during test execution - this way the job is executed in the same thread as the test and shares the transaction.

The task executor configuration can be moved to a separate spring configuration xml file. Have two versions of it - one with SyncTaskExecutor which is used during testing and the other AsyncTaskExecutor that is used for production runs.

gkamal
  • 20,777
  • 4
  • 60
  • 57
  • I think I haven't understood your solution completelyL please be more verbose. I don't process any files: the processing step reads something from DB and writes something back to DB. The initialization step needs to initialize DB with some values. This can't be done as separate SQL script (perhaps you referred that as "file"), as DB objects are complicated and should be persisted using DAO. As DAO methods are called from main thread, the changes are not visible to SUT. – dma_k Apr 26 '11 at 16:19
  • I was talking about the spring configuration file. You can move the task executor configuration to a separate spring xml file. You will have two versions of this xml file - one used for testing will use the SyncTaskExecutor that launches the job in the same thread - so the transaction started by the test is propagated to the job as well. The other version will contain the AsyncTaskExecutor which will be used for the real application execution. – gkamal Apr 26 '11 at 17:41
  • Yes, that's doable, thanks for noting this. What about integration steps, when I want to make end-to-end testing with production context / AsyncTaskExecutor? – dma_k Apr 27 '11 at 14:42
2

Although this is not a true solution to your question, I found it possible to start a new transaction inside a worker thread manually. In some cases this might be sufficient.

Source: Spring programmatic transactions.

Example:

@PersistenceContext
private EntityManager entityManager;
@Autowired
private PlatformTransactionManager txManager;

/* in a worker thread... */
public void run() {
    TransactionStatus tx = txManager.getTransaction(new DefaultTransactionDefinition());
    try {
        entityManager.find(...)
        ...
        entityManager.flush(...)
        etc...
        txManager.commit(tx);
    } catch (RuntimeException e) {
        txManager.rollback(tx);
    }
}
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • If you go that way, it's better to use `TransactionTemplate`. – dma_k May 14 '12 at 13:28
  • 2
    @rustyx, but it is not _propagation_ of transaction to another thread - it creates new instead. Is there a way to make worker thread participate in the transaction created by main thread? – surlac Oct 11 '13 at 08:19
1

If you do want separate configurations, I'd recommend templating the isolation policy in your configuration and getting its value out of a property file so that you don't wind up with a divergent set of Spring configs for testing and prod.

But I agree that using the same policy production uses is best. How vast is your fixture data, and how bad would it be to have a setUp() step that blew away and rebuilt your data (maybe from a snapshot, if it's a lot of data) so that you don't have to rely on rollbacks?

dfichter
  • 1,078
  • 8
  • 9
  • Thanks for comment. As the test data (DB state) should be different for each test in Unit Test class, I would like to setup DB at the beginning of each test and commit it, but I failed to find any way to get `TransactionStatus` to pass to `PlatformTransactionManager#commit()`. I believe, the only way out is to have Unit Test class that consists of one `setUp()` and one `test()` method and then in `setUp()` somehow programmatically starts Hibernate transaction. I already imagine that ugly code... – dma_k Mar 09 '11 at 15:54