You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java新手疑问:JUnit测试应针对AccountService接口还是实现类?

关于JUnit+Mockito测试AccountService的困惑解答

嘿,作为Java新手刚接触JUnit和Mockito时,纠结测试接口还是实现类太正常啦!我来给你掰扯清楚:

你应该测试实现类AccountServiceImpl,而不是AccountService接口。原因很简单:

  • 接口只是定义了方法的契约(要做什么),没有任何实际的业务逻辑代码,测试它根本没法验证功能是否正确;
  • 所有的业务逻辑、数据操作、异常判断都在AccountServiceImpl里,你要验证的就是这些逻辑是否符合预期,同时也能间接确认实现类是否遵守了接口的契约。

接下来给你梳理下具体的测试思路,结合Mockito来处理依赖:

核心测试场景(以AccountServiceImpl为例)

因为你的实现类依赖了AccountRepository、OperationRepository和OperationConverter,我们可以用Mockito来模拟这些依赖,避免操作真实数据库,让测试更高效、独立。

1. 测试deposit方法

需要覆盖这些场景:

  • 正常存款:验证余额更新正确,操作记录被保存,返回的Operation对象符合预期
  • 存款金额为负数:抛出OperationException
  • 目标账户不存在:抛出AccountException

这里给你写个简单的测试示例:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class AccountServiceImplTest {

    @Mock
    private AccountRepository accountRepository;
    @Mock
    private OperationRepository operationRepository;
    @Mock
    private OperationConverter operationConverter;

    @InjectMocks
    private AccountServiceImpl accountService; // 测试实现类

    @Test
    void deposit_ValidAmountAndExistingAccount_ReturnsOperation() {
        // 1. 准备测试数据
        String accountNumber = "ACC123";
        int depositAmount = 100;
        AccountEntity mockAccount = new AccountEntity();
        mockAccount.setNumber(accountNumber);
        mockAccount.setBalance(200);
        OperationEntity savedOperation = new OperationEntity();
        savedOperation.setBalance(300);
        Operation mockReturnedOperation = new Operation();
        mockReturnedOperation.setBalance(300);

        // 2. 模拟依赖行为
        when(accountRepository.findByNumber(accountNumber)).thenReturn(mockAccount);
        when(operationRepository.save(any(OperationEntity.class))).thenReturn(savedOperation);
        when(operationConverter.convert(savedOperation)).thenReturn(mockReturnedOperation);

        // 3. 执行测试方法
        Operation result = accountService.deposit(accountNumber, depositAmount);

        // 4. 验证结果
        assertEquals(300, result.getBalance());
        verify(accountRepository, times(1)).save(mockAccount); // 确认账户余额被更新
        verify(operationRepository, times(1)).save(any(OperationEntity.class)); // 确认操作记录被保存
    }

    @Test
    void deposit_NegativeAmount_ThrowsOperationException() {
        // 验证金额为负时抛出异常
        assertThrows(OperationException.class, () -> accountService.deposit("ACC123", -50));
    }

    @Test
    void deposit_NonExistingAccount_ThrowsAccountException() {
        when(accountRepository.findByNumber("ACC999")).thenReturn(null);
        assertThrows(AccountException.class, () -> accountService.deposit("ACC999", 100));
    }
}

2. 测试withdraw方法

类似地,要覆盖:

  • 正常取款成功,余额更新正确
  • 取款金额超过当前余额:抛出OperationException
  • 取款金额为负数:抛出OperationException
  • 账户不存在:抛出AccountException

3. 测试history方法

  • 账户存在且有操作记录:返回包含对应操作的OperationsDto
  • 账户存在但无操作记录:返回空的或空列表的OperationsDto
  • 账户不存在:抛出AccountException

额外小贴士

  • 测试时要专注于业务逻辑的验证,而不是依赖的内部实现(比如不用关心Repository怎么保存数据,只需要确认它被调用了正确的次数、传入了正确的参数)
  • 用Mockito的verify方法来确认依赖方法是否被正确调用,用when来模拟依赖的返回值或异常

内容的提问来源于stack exchange,提问作者Amadou Beye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:27:24