应用层服务测试要点?附业务服务代码求单元测试方案
应用层服务的单元测试策略与实践
我完全理解你的困惑——这种纯编排领域逻辑的应用层服务看起来没什么实质性逻辑可测,但其实它的核心职责就是正确协调领域层和基础设施层的依赖,所以单元测试还是有明确的目标的。结合你给出的execute方法,我来拆解一下这类服务的测试要点和实践方法:
一、应用层服务需要测试的核心内容
这类服务的单元测试不用关注领域层的业务逻辑(那是领域对象单元测试的责任),重点要验证的是编排逻辑的正确性:
- 依赖交互的正确性:确保各个依赖(仓储、领域对象)被调用的顺序、次数、参数完全符合预期
- 异常传递的正确性:验证领域层或仓储抛出的异常(比如
StaffNotFound、WrongCargoReleaseCode等)能被正确向上传递,没有被意外捕获或丢失 - 返回值的合规性:确认方法返回了符合要求的DTO实例,类型和结构正确
- 领域行为的触发:确保领域对象的核心方法(比如
staff->releaseCargo)被正确调用,应用层完成了触发领域逻辑的职责
二、针对这类服务编写单元测试的步骤与示例
以PHPUnit为例,这类测试的核心是用Mock隔离所有外部依赖,只验证被测服务的编排逻辑:
1. 成功场景测试示例
public function testExecuteSuccessfullyTriggersCargoRelease() { // 1. 准备测试数据与DTO $staffIdString = 'staff-1001'; $releaseCodeString = 'REL-2024-001'; $request = new ReleaseItemRequestDTO(); $request->authenticatedStaffId = $staffIdString; $request->releaseCode = $releaseCodeString; $staffId = StaffEntityId::from($staffIdString); $releaseCode = ReleaseCode::from($releaseCodeString); // 2. 创建Mock依赖 $mockStaff = $this->createMock(Staff::class); $mockCargo = $this->createMock(Cargo::class); $mockStaffRepo = $this->createMock(StaffRepository::class); $mockStaffRepo->method('findById') ->with($staffId) ->willReturn($mockStaff); $mockCargoRepo = $this->createMock(CargoRepository::class); $mockCargoRepo->method('findByReleaseCode') ->with($releaseCode) ->willReturn($mockCargo); // 3. 设置预期的交互行为 // 断言领域方法被正确调用 $mockStaff->expects($this->once()) ->method('releaseCargo') ->with($mockCargo, $releaseCode); // 断言仓储的save方法被调用 $mockCargoRepo->expects($this->once()) ->method('save') ->with($mockCargo); // 4. 执行被测服务 $releaseService = new YourReleaseItemService($mockStaffRepo, $mockCargoRepo); $response = $releaseService->execute($request); // 5. 断言返回值 $this->assertInstanceOf(AcknowledgmentResponseDTO::class, $response); }
2. 异常场景测试示例(以StaffNotFound为例)
public function testExecuteThrowsStaffNotFoundWhenStaffDoesNotExist() { // 1. 准备测试数据 $nonExistentStaffId = 'staff-9999'; $request = new ReleaseItemRequestDTO(); $request->authenticatedStaffId = $nonExistentStaffId; $request->releaseCode = 'REL-2024-001'; $staffId = StaffEntityId::from($nonExistentStaffId); // 2. Mock仓储抛出异常 $mockStaffRepo = $this->createMock(StaffRepository::class); $mockStaffRepo->method('findById') ->with($staffId) ->willThrowException(new StaffNotFound()); $mockCargoRepo = $this->createMock(CargoRepository::class); // 3. 执行并断言异常 $releaseService = new YourReleaseItemService($mockStaffRepo, $mockCargoRepo); $this->expectException(StaffNotFound::class); $releaseService->execute($request); }
3. 其他异常场景的测试思路
对于CargoNotFound、WrongCargoReleaseCode等异常,测试逻辑类似:
- Mock对应的依赖抛出目标异常
- 执行
execute方法,断言方法会抛出对应的异常 - 确保其他依赖的方法不会被意外调用(比如如果Staff不存在,就不应该调用cargoRepository的方法)
三、总结
这类应用层服务的单元测试核心是验证编排的准确性:你不需要测试releaseCargo里的业务逻辑(那是Staff类的单元测试要做的),只需要确保应用层正确地调用了这个方法,传递了正确的参数,并且正确处理了依赖返回的结果和异常。这样的测试既能保证应用层的逻辑正确性,又不会和领域层的测试重复。
内容的提问来源于stack exchange,提问作者a4w
相关产品推荐
相关产品推荐

