0
点赞
收藏
分享

微信扫一扫

英语作桥梁文化传世界 学好英语传承中华传统文化

云朵里的佛光 2023-10-18 阅读 35

整体代码示例

首先,为了简化,我们让服务层就是简单的类,然后使用Id查找用户,这个和之前测试UserService接口不太一样哦:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

现在,我们要模拟UserRepository的行为,使其在尝试获取用户时引发一个异常。这里我们使用Mockito进行模拟:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

//之前我们是定义了一个UserService接口,现在简化成UserService类了哈
    @InjectMocks
    private UserService userService;

    @Mock
    private UserRepository userRepository;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

//重点,后文详解!
    @Test(expected = DatabaseConnectionException.class)
    public void testGetUserByIdWithDbError() {
        when(userRepository.findById(anyLong())).thenThrow(new DatabaseConnectionException("Database connection failed!"));

        userService.getUserById(1L);
    }
}

//重点,后文详解!
class DatabaseConnectionException extends RuntimeException {
    public DatabaseConnectionException(String message) {
        super(message);
    }
}

在上述测试中,我们模拟了userRepository.findById()方法,使其抛出DatabaseConnectionException异常。然后,我们在测试方法上使用@Test(expected = DatabaseConnectionException.class)来表示我们期望该方法引发此异常

这样,如果getUserById方法在遇到此异常时没有正确处理,测试将失败。这确保了即使在面对意外的数据库问题时,我们的代码仍能按预期的方式运行(在这种情况下,按预期抛出异常)。


到底在模拟什么?到底在测试什么?

下面,我们进一步说明:

综上所述,这个测试确保了当底层UserRepository出现数据库连接错误时,上层的UserService可以正确地传递这个错误。这对于后续的异常处理很重要,例如:在Controller层将这个异常转化为一个友好的错误消息返回给用户。


什么时候测试失败?

但是,以下几种情况可能导致测试不通过:

  1. 异常被吞没:如果Service层调用了Repository的方法,但内部捕获了该异常并没有重新抛出,那么测试就会失败。例如:

    public User getUserById(Long id) {
        try {
            return userRepository.findById(id);
        } catch (DatabaseConnectionException e) {
            // 异常被吞没了
            return null;
        }
    }
    

  2. 调用的方法不正确:如果Service层没有调用预期的Repository方法,而是调用了其他方法,或者完全没有调用,那么模拟的异常就不会被触发,导致测试失败。

  3. 模拟的不正确:如果在测试中模拟的方法或参数与实际调用的方法或参数不匹配,那么模拟的异常也不会被触发。例如,如果Service实际上是这样调用的:userRepository.findById(2L),但我们的模拟是这样的:when(userRepository.findById(1L))...,那么异常就不会被触发。

    • 其他未预料到的异常:有时可能会有其他的未被预料到的异常被抛出,这也会导致测试失败。

因此,虽然大多数情况下,如果Repository层方法抛出了异常,Service层应该也会抛出,但还是存在一些情况导致测试不通过,这也是进行此类测试的原因。


Exception 异常类定义

class DatabaseConnectionException extends RuntimeException {
    public DatabaseConnectionException(String message) {
        super(message);
    }
}

这里,我们定义了一个继承自RuntimeException的新异常类DatabaseConnectionExceptionRuntimeException是Java中所有非检查型异常的基类。所谓“非检查型”是指编译器不强制我们捕获或声明它。这与Exception(检查型异常)相对。

关于DatabaseConnectionException类的解释:

这种自定义异常,通常在我们希望为特定的错误情况定义更具描述性的异常名时使用,或者当我们想为特定的异常情况添加更多上下文信息时使用,信息越多,测试反馈的效果越好,所以一般使用自定义异常,继承RuntimeException!下面我们讨论一下,为什么建议使用RuntimeException?


RuntimeException 使用意义

下面是一些选择使用RuntimeException的原因:

然而,这并不意味着总是应该选择非检查型异常。有时,如果你希望调用者必须处理某个特定的异常,使用检查型异常可能更合适。选择使用哪种异常是基于特定上下文和需求的决策。但在许多现代Java应用程序中,倾向于使用RuntimeException因为它提供了更大的灵活性和简洁性。


总结

模拟异常的目的

真正的数据库异常是不是Runtime异常

在Java中,数据库操作可能会抛出多种异常。其中,SQLException 是一个受检异常(checked exception)。

但在很多现代的框架中(如Spring),这些受检异常通常会被转换成运行时异常(runtime exceptions),这样可以使代码更为简洁,避免了过多的try-catch块。

举报

相关推荐

0 条评论