单元测试服务bean使用Spring + JPA但没有DB可用

时间:2014-06-24 06:04:26

标签: java spring unit-testing jpa junit

一个简单的银行应用程序: enter image description here

注意事项:

  • 将Spring + JPA与EclipseLink一起用作JPA提供程序
  • 使用@PersistenceContext
  • 将EntityManager注入BaseDaoImpl
  • DAO自动装入Service bean
  • 服务方法中使用的@Transactional注释

目标:单元测试服务方法作为maven构建的一部分

单元测试=简单的API测试。例如,服务方法:transfer(int fromAccountId, int toAccountId, double amount)具有单元测试用例:

  • fromAccountId不应为0
  • toAccountId不应为0
  • fromAccountId!= toAccountId
  • `数量大于0

这些"单元测试"案例不需要数据库连接。

问题:构建服务器没有数据库设置。但是,当执行单元测试用例时,Spring会尝试连接到失败的DB。但是,我们并不需要DB连接来完成这些情况。 (我们有另一组"集成案例" - 这些不是作为正常构建的一部分执行,而是在完全可用的环境下手动执行。如何? - 参见this thread

问题:

  • 执行这些单元测试用例的最佳做法是什么?
  • 我们可以强制Spring不要在实际需要的最后一刻进行数据库连接吗? (现在,当它遇到@Transactional方法时会发生)

按要求添加服务层代码:

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);
    }

    ...    

}

3 个答案:

答案 0 :(得分:1)

您需要的是距离集成测试不远的地方。您首先必须构建虚拟 PlatformManager,并确保它用于您的测试。您可以在另一篇关于SO How do I mock a TransactionManager in a JUnit test, (outside of the container)?的帖子和下面的另一个(部分)示例中找到相关的线索。

当spring按顺序应用ApplicationContext定义时,最后覆盖其他定义,你只需添加为你测试xml(或JavaConfig)文件在最后一个地方声明那个假{{1}与普通配置中的bean名称相同。

然后从应用程序上下文中获取服务bean并用模拟替换它的Dao(Mockito或你喜欢的)。

根据您必须测试的内容,您必须调整虚拟PlatformManager,但如果只是添加:

PlatformManager

您将能够控制事务是否已启动,已提交或已回滚。如果您有特殊要求,则必须创建一个真实的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; } ,而不是传递空值。

答案 1 :(得分:1)

这里的目的是在单元测试期间禁用DB连接。一种方法是按照@Serge Ballesta的回答中提到的Mock事务管理器。我们发现了一种更简单的方法 - 我们在单元测试期间完全禁用事务管理器的加载。这可以通过在应用程序上下文中注释掉下面的行来完成,这可以防止基于注释的事务被踢入。

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

答案 2 :(得分:0)

我建议您放弃完全为数据库交互编写单元测试。

Thisthis博客文章解释了原因。

当您使用Spring时,在测试环境中使用内存数据库并继续编写有意义的集成测试非常容易。

在您要为BankServiceImpl编写单元测试的用例中,根本不需要涉及Spring。

您需要做的就是自己创建该服务并将相关的模拟注入其中。

编写这样的测试将使您免于模拟与Spring相关的服务(如事务管理),并且还将使测试变得更快,因为不需要创建Spring上下文。