gammay
gammay

Reputation: 6215

Unit-testing Service bean which uses Spring+JPA but no DB is available

A simple banking application: enter image description here

Points to note:

Objective: Unit-test Service methods as part of maven build

Unit-test = simple API testing. For example, a service method: transfer(int fromAccountId, int toAccountId, double amount) has unit-test cases:

These "unit-test" cases do not require DB connection.

Problem: Build server has no DB setup. However, when the unit-test case is executed, Spring tries to connect to DB which fails. However, we do not really need DB connection for these cases to go through. (We have another set of "integration cases" - these are not executed as part of the normal build but will be executed manually with full environment available. How? - See this thread)

Questions:


Adding Service Layer code as requested:

public class BankManagerImpl implements BankManager {

    @Autowired
    AccountDao accountDao;

    @Autowired
    TransactionDao transactionDao;

    ...

    @Override
    @Transactional
    public void deposit(int accountId, double amount) {
        Account a = accountDao.getAccount(accountId);
        double bal = a.getAmount();
        bal = bal + amount;
        a.setAmount(bal);

        accountDao.updateAccount(a);

        transactionDao.addTransaction(a, TransactionDao.DEPOSIT, amount);
    }

    @Override
    @Transactional
    public void withdraw(int accountId, double amount) {
        Account a = accountDao.getAccount(accountId);

        double bal = a.getAmount();
        if(bal < amount) {
            throw new RuntimeException("insufficient balance");
        }

        bal = bal - amount;
        a.setAmount(bal);

        accountDao.updateAccount(a);

        transactionDao.addTransaction(a, TransactionDao.WITHDRAW, amount);
    }

    @Override
    @Transactional
    public void transfer(int fromAccountId, int toAccountId, double amount) {
        withdraw(fromAccountId, amount);
        deposit(toAccountId, amount);
    }

    ...    

}

Upvotes: 1

Views: 1879

Answers (3)

geoand
geoand

Reputation: 64079

I would suggest that you forgo writing unit tests for the database interaction altogether.

This and this blog posts explain why.

When you are using Spring, it's so easy to just use an in-memory database in the testing environment and then proceed to write meaningful integration tests.

In your use case where you want to write a unit test for BankServiceImpl, there is no need to get Spring involved at all.

All you need to do is create that service on your own and inject the relevant mocks into it.

Writing such a test will spare you from having to mock Spring-related services (like transaction management) and will also make the test a heck of a lot quicker, since no spring context will ever need to be created.

Upvotes: 0

gammay
gammay

Reputation: 6215

The intention here is to disable DB connection during unit tests. One way to go is to Mock transaction manager as mentioned in @Serge Ballesta's answer. We found a simpler approach - we disable loading of transaction manager altogether during unit tests. This can be done by commenting out below line in application context, which prevents annotation based transactions from getting kicked in.

<!-- <tx:annotation-driven transaction-manager="transactionManager" /> -->

Upvotes: 1

Serge Ballesta
Serge Ballesta

Reputation: 149175

What you need is not far from an integration test. You first have to build a dummy PlatformManager and make sure it is used for you test. You will find clues for that in this other post on SO How do I mock a TransactionManager in a JUnit test, (outside of the container)? and another (partial) example below.

As spring applies ApplicationContext definition in order, the last overriding the others, you just add for you test an xml (or JavaConfig) file in last place declaring that dummy PlatformManager whith the same bean name that it has in normal config.

Then you get your service bean from the application context and replace its Dao with a mock (Mockito or what you like).

Depending of what you have to test, you will have to tweak the dummy PlatformManager, but if you simply add :

public class MockedTransactionManager implements PlatformTransactionManager {
public boolean transactionStarted = false
public commited = false;
public rollbacked = false;

@Override
public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
    transactionStarted = true;
    return null;
}

@Override
public void commit(TransactionStatus status) throws TransactionException {
    commited = true;
}

@Override
public void rollback(TransactionStatus status) throws TransactionException {
    commited = true;
}

you will be able to control if transaction are started, commited or rollbacked. If you have special requirement, you could have to create a real SimpleTransactionStatus instead of passing a null.

Upvotes: 1

Related Questions