Spring Boot 在我的服务单元测试中抛出了我的自定义异常,但我看不出它这样做的原因

问题描述 投票:0回答:0

所以我有一个我正在处理的小应用程序,它有 3 个实体设备、StakeLimit(它有设备外键)和 TicketMessage(这个与问题无关),它有几个服务、控制器等。问题在 StakeLimitService 中,特别是在我正在编写的 StakeLimitServiceTest 单元测试中。问题出在我正在测试的方法中,它一直抛出我的自定义异常 DeviceNotFoundException ,当在存储库中找不到提供的 id 的设备时抛出该异常,问题是它不应该抛出异常但它确实抛出异常。我尝试了一切方法来摆脱它,包括使用 chatGPT 以期找到解决方案,但我只让它与一种解决方案一起工作,这对我来说没有任何意义,但我原来的解决方案却没有(我在底部提供了这个) ).

这是我的代码: 设备:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity(name = "Device")
@Table(name = "devices")
public class Device {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(
            name = "id",
            updatable = false
    )
    private UUID id;

    @Column(name = "blocked")
    private boolean blocked;

    @Column(name = "restriction_expires")
    private boolean restrictionExpires;

    @Column(name = "restriction_expires_at")
    private LocalDateTime restrictionExpiresAt;
}

设备库:

@Repository
public interface DeviceRepository extends JpaRepository<Device, UUID> {

    Device findDeviceById(UUID id);
}

股权限制:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity(name = "StakeLimit")
@Table(name = "stake_limits")
public class StakeLimit {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(
            name = "id",
            updatable = false
    )
    private UUID id;

    @ManyToOne
    @JoinColumn(
            name = "device_id",
            nullable = false,
            referencedColumnName = "id",
            foreignKey = @ForeignKey(
                    name = "device_id_fk"
            )
    )
    private Device device;

    @Column(
            name = "time_duration",
            nullable = false
    )
    private Integer timeDuration;

    @Column(
            name = "stake_limit",
            nullable = false
    )
    private Double stakeLimit;

    @Column(
            name = "hot_amount_pctg",
            nullable = false
    )
    private Integer hotAmountPctg;

    @Column(
            name = "restr_expiry",
            nullable = false
    )
    private Integer restrExpiry;
}

StakeLimitRepository:

public interface StakeLimitRepository extends JpaRepository<StakeLimit, UUID> {

    boolean existsByDevice(Device device);
    StakeLimit findByDevice(Device device);

}

StakeLimitRequest:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StakeLimitRequest {
    //Using String instead of UUID because I could not get my custom validation to work
    @NotEmpty(message = "DeviceId field can't be empty")
    private String deviceId;

    @Min(value = 300, message = "Minimum time duration is 5 minutes (300 seconds)")
    @Max(value = 86400, message = "Maximum time duration is 24 hours (86400 seconds)")
    private Integer timeDuration;

    @Min(value = 1, message = "Minimum stake limit is 1")
    @Max(value = 10000000, message = "Maximum stake limit is 10000000")
    private Double stakeLimit;

    @Min(value = 1, message = "Minimum hot amount percentage is 1")
    @Max(value = 100, message = "Maximum hot amount percentage is 100")
    private Integer hotAmountPctg;

    @Min(value = 0, message = "Minimum restriction expiry is 1 minute (60 seconds)" +
            " while maximum is 0 seconds (it never expires)")
    private Integer restrExpiry;
}

StakeLimitResponse:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StakeLimitResponse {
    private Integer timeDuration;
    private Double stakeLimit;
    private Integer hotAmountPctg;
    private Integer restrExpiry;
}

StakeLimitService:

@Service
@RequiredArgsConstructor
public class StakeLimitService {

    private final StakeLimitRepository stakeLimitRepository;
    private final DeviceRepository deviceRepository;

    public StakeLimitResponse getStakeLimit(String deviceId) {
        //method for checking if provided deviceId is actually UUID (for more info
        //check comments above isUuidValid method)
        UUID deviceUuid = isUuidValid(deviceId);
        //then check if device by that id exists in our devices table
        if (!deviceRepository.existsById(deviceUuid)) {
            //if it does not throw exp
            throw new DeviceNotFoundException("Device with " + deviceUuid + " id not found");
        }
        //else check if that device contains stake limits
        if (!stakeLimitRepository.existsByDevice(deviceRepository.findDeviceById(deviceUuid))) {
            //if not throw exp
            throw new StakeLimitNotFoundException("Stake limit with device " + deviceUuid + " id not found");
        }

        //else get device and then get our stake limit using that device
        var device = deviceRepository.findDeviceById(deviceUuid);
        var stakeLimit = stakeLimitRepository.findByDevice(device);
        //and finally build our response and return it to user
        return StakeLimitResponse.builder()
                .timeDuration(stakeLimit.getTimeDuration())
                .stakeLimit(stakeLimit.getStakeLimit())
                .hotAmountPctg(stakeLimit.getHotAmountPctg())
                .restrExpiry(stakeLimit.getRestrExpiry())
                .build();
    }

    public StakeLimitResponse addStakeLimit(StakeLimitRequest request) {
        //same as above
        UUID deviceId = isUuidValid(request.getDeviceId());
        //Could also make it if deviceId does not exist, create device with that id
        if (!deviceRepository.existsById(deviceId)) {
            throw new DeviceNotFoundException("Device with " + deviceId + " id not found");
        }
        if (stakeLimitRepository.existsByDevice(deviceRepository.findDeviceById(deviceId))) {
            throw new DeviceAlreadyExistsException(
                    "Stake limit with device " + deviceId + " id already exists"
            );
        }
        var device = deviceRepository.findDeviceById(deviceId);
        //when all checks are completed we finally check if restriction is set to
        //expire or not and set it to our device and save it to our db again
        device.setRestrictionExpires(request.getRestrExpiry() != 0);
        deviceRepository.save(device);

        //then we build our stake limit and save it to db
        var stakeLimit = StakeLimit.builder()
                .device(device)
                .timeDuration(request.getTimeDuration())
                .stakeLimit(request.getStakeLimit())
                .hotAmountPctg(request.getHotAmountPctg())
                .restrExpiry(request.getRestrExpiry())
                .build();
        stakeLimitRepository.save(stakeLimit);

        //finally build our response and return it
        return StakeLimitResponse.builder()
                .timeDuration(stakeLimit.getTimeDuration())
                .stakeLimit(stakeLimit.getStakeLimit())
                .hotAmountPctg(stakeLimit.getHotAmountPctg())
                .restrExpiry(stakeLimit.getRestrExpiry())
                .build();
    }

    @Transactional
    public StakeLimitResponse changeStakeLimit(
            String deviceId,
            Integer timeDuration,
            Double stakeLimit,
            Integer hotAmountPctg,
            Integer restrExpiry
    ) {
        //same checks as the above
        UUID deviceUuid = isUuidValid(deviceId);
        if (!deviceRepository.existsById(deviceUuid)) {
            throw new DeviceNotFoundException("Device with " + deviceUuid + " id not found");
        }
        if (!stakeLimitRepository.existsByDevice(deviceRepository.findDeviceById(deviceUuid))) {
            throw new StakeLimitNotFoundException("Stake limit for device with id " + deviceUuid + " not found");
        }
        var device = deviceRepository.findDeviceById(deviceUuid);
        var stakeLimitDB = stakeLimitRepository.findByDevice(device);

        //then we check which fields user actually sent (decided to change) and change them
        if (timeDuration != null && !timeDuration.equals(stakeLimitDB.getTimeDuration())) {
            stakeLimitDB.setTimeDuration(timeDuration);
        }
        if (stakeLimit != null && !stakeLimit.equals(stakeLimitDB.getStakeLimit())) {
            stakeLimitDB.setStakeLimit(stakeLimit);
        }
        if (hotAmountPctg != null && !hotAmountPctg.equals(stakeLimitDB.getHotAmountPctg())) {
            stakeLimitDB.setHotAmountPctg(hotAmountPctg);
        }
        if (restrExpiry != null && !restrExpiry.equals(stakeLimitDB.getRestrExpiry())) {
            device.setRestrictionExpires(restrExpiry != 0);
            deviceRepository.save(device);
            stakeLimitDB.setRestrExpiry(restrExpiry);
        }
        stakeLimitRepository.save(stakeLimitDB);

        return StakeLimitResponse.builder()
                .timeDuration(stakeLimitDB.getTimeDuration())
                .stakeLimit(stakeLimitDB.getStakeLimit())
                .hotAmountPctg(stakeLimitDB.getHotAmountPctg())
                .restrExpiry(stakeLimitDB.getRestrExpiry())
                .build();
    }

    //method used for checking if provided UUID is correct
    //because I could not get custom validation to work properly I switched all requests
    //to take String instead of pure UUID, and then i check if that String matches
    //UUID pattern, if it does not match pattern throw custom exception else
    //transform provided String into actual UUID
    private UUID isUuidValid(String uuid) {
        final String uuid_pattern = "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$";

        if (!uuid.matches(uuid_pattern)) {
            throw new UuidNotValidException(uuid + " UUID not valid");
        }

        return UUID.fromString(uuid);
    }
}

StakeLimitServiceTest:

@ExtendWith(MockitoExtension.class)
class StakeLimitServiceTest {

    @Mock
    private StakeLimitRepository stakeLimitRepository;

    @Mock
    private DeviceRepository deviceRepository;

    @InjectMocks
    private StakeLimitService stakeLimitService;

    private StakeLimit stakeLimit;
    private StakeLimitRequest stakeLimitRequest;
    private StakeLimitResponse stakeLimitResponse;
    private Device device;

    @BeforeEach
    void setUp() {
        device = Device.builder().id(UUID.fromString("799de2ee-13c2-40a1-8230-d7318de97925")).build();
        stakeLimit = StakeLimit.builder()
                .device(device)
                .timeDuration(1800)
                .stakeLimit(999.0)
                .hotAmountPctg(80)
                .restrExpiry(300)
                .build();
        stakeLimitRequest = StakeLimitRequest.builder()
                .deviceId(device.getId().toString())
                .timeDuration(1800)
                .stakeLimit(999.0)
                .hotAmountPctg(80)
                .restrExpiry(300)
                .build();
        stakeLimitResponse = StakeLimitResponse.builder()
                .timeDuration(1800)
                .stakeLimit(999.0)
                .hotAmountPctg(80)
                .restrExpiry(300)
                .build();
    }

    @Test
    void Should_ReturnStakeLimitResponse_When_GetStakeLimit() {
        // when
        when(deviceRepository.findDeviceById(device.getId())).thenReturn(device);
        when(stakeLimitRepository.findByDevice(device)).thenReturn(stakeLimit);

        StakeLimitResponse response = stakeLimitService.getStakeLimit(device.getId().toString());

        // then
        assertThat(response).isNotNull();
        verify(deviceRepository).findDeviceById(any());
        verify(stakeLimitRepository).findByDevice(any());
    }

    @Disabled
    @Test
    void Should_CreateStakeLimitAndReturnStakeLimitResponse_When_AddStakeLimit() {
        // given
        stakeLimitRequest.setDeviceId(device.getId().toString());
        // when
        when(deviceRepository.findDeviceById(device.getId())).thenReturn(device);
        when(stakeLimitRepository.save(any(StakeLimit.class))).thenReturn(stakeLimit);

        StakeLimitResponse savedStakeLimit = stakeLimitService.addStakeLimit(stakeLimitRequest);

        // then
        assertThat(savedStakeLimit).isNotNull();
        verify(deviceRepository).findDeviceById(any());
        verify(stakeLimitRepository).save(any());
    }

    @Disabled
    @Test
    void Should_ChangeStakeLimitAndReturnStakeLimitResponse_When_ChangeStakeLimit() {
    }
}

也出于某种原因,当我尝试创建新设备时

Device device = new Device()
我的设备的 id 为 null 而不是自动生成 UUID,这也是我自己构建设备并传递 UUID 的原因。

唯一有用的是当我切换

UUID deviceUuid = isUuidValid(deviceId);
        if (!deviceRepository.existsById(deviceUuid)) {
            throw new DeviceNotFoundException("Device with " + deviceUuid + " id not found");
        }
        if (!stakeLimitRepository.existsByDevice(deviceRepository.findDeviceById(deviceUuid))) {
            throw new StakeLimitNotFoundException("Stake limit with device " + deviceUuid + " id not found");
        }

        var device = deviceRepository.findDeviceById(deviceUuid);
        var stakeLimit = stakeLimitRepository.findByDevice(device);

对于

Device device = deviceRepository.findDeviceById(deviceId);
        UUID deviceUuid = isUuidValid(deviceId);
        Device device = deviceRepository.findDeviceById(deviceUuid);
        if (device == null) {
            throw new DeviceNotFoundException("Device with " + deviceUuid + " id not found");
        }

        StakeLimit stakeLimit = stakeLimitRepository.findByDevice(device);
        if (stakeLimit == null) {
            throw new StakeLimitNotFoundException("Stake limit not found for device " + deviceUuid);
        }

chatGPT 建议的,我不知道为什么这个解决方案有效,但当他们基本上做同样的事情时,我的却没有。

java spring-boot unit-testing junit5 spring-boot-test
© www.soinside.com 2019 - 2024. All rights reserved.