借助于使用接口管理依赖的方案,我们在对模块进行单元测试时,可以自由配置 mock 依赖,而且无需 hook 模块内部的代码。
例如这样一个依赖于网络模块的登陆模块:
// 登录模块
class LoginService {
func login(account: String, password: String, completion: (Result<LoginError>) -> Void) {
// 内部使用 RequiredNetServiceInput 进行网络访问
let netService = Router.makeDestination(to: RoutableService<RequiredNetServiceInput
>())
let request = makeLoginRequest(account: account, password: password)
netService?.POST(request: request, completion: completion)
}
}
// 声明依赖
extension RoutableService where Protocol == RequiredNetServiceInput {
init() {}
}
Objective-C Sample
// 登录模块
@interface LoginService : NSObject
@end
@implementation LoginService
- (void)loginWithAccount:(NSString *)account password:(NSString *)password completion:(void(^)(Result *result))completion {
// 内部使用 RequiredNetServiceInput 进行网络访问
id<RequiredNetServiceInput> netService = [ZIKRouterToService(RequiredNetServiceInput) makeDestination];
Request *request = makeLoginRequest(account, password);
[netService POSTRequest:request completion: completion];
}
@end
// 声明依赖
@protocol RequiredNetServiceInput <ZIKServiceRoutable>
- (void)POSTRequest:(Request *)request completion:(void(^)(Result *result))completion;
@end
在编写单元测试时,不需要引入真实的网络模块,可以提供一个自定义的 mock 网络模块:
class MockNetService: RequiredNetServiceInput {
func POST(request: Request, completion: (Result<NetError>) {
completion(.success)
}
}
// 注册 mock 依赖
ZIKAnyServiceRouter.register(RoutableService<RequiredNetServiceInput>(),
forMakingService: MockNetService.self) { (config, router) -> EditorViewProtocol? in
return MockNetService()
}
Objective-C Sample
@interface MockNetService : NSObject <RequiredNetServiceInput>
@end
@implementation MockNetService
- (void)POSTRequest:(Request *)request completion:(void(^)(Result *result))completion {
completion([Result success]);
}
@end
// 注册 mock 依赖
[ZIKServiceRouter registerServiceProtocol:ZIKRoutable(EditorViewInput) forMakingService:[MockNetService class]];
对于那些没有接口交互的外部依赖,例如只是简单的跳转到对应界面,则只需注册一个空白的 proxy。
单元测试代码:
class LoginServiceTests: XCTestCase {
func testLoginSuccess() {
let expectation = expectation(description: "end login")
let loginService = LoginService()
loginService.login(account: "account", password: "pwd") { result in
expectation.fulfill()
}
waitForExpectations(timeout: 5, handler: { if let error = $0 {print(error)}})
}
}
Objective-C Sample
@interface LoginServiceTests : XCTestCase
@end
@implementation LoginServiceTests
- (void)testLoginSuccess {
XCTestExpectation *expectation = [self expectationWithDescription:@"end login"];
[[LoginService new] loginWithAccount:@"" password:@"" completion:^(Result *result) {
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError * _Nullable error) {
!error? : NSLog(@"%@", error);
}];
}
@end
使用接口管理依赖,可以更容易 mock,剥除外部依赖对测试的影响,让单元测试更稳定。