又一个 HTTP VCR
这是列表中新增的一个 VCR 工具。功能和操作从 VCR.py 中借鉴。
这是一个非常简单的 HTTP(S) 假装工具,仅实现 VCR 录制和回放功能(无需使用第三方模拟工具)。
这是单页文档,但也可在Wiki 的独立页面上找到。
配置
库的配置非常简单。所有配置都可以在单例 VCR 实例上使用 +setupWithConfiguration:
方法进行指定,并在插入磁带期间使用 +insertCassetteWithConfiguration:
方法进行调整。
配置由 YHVConfiguration 类表示,并具有以下参数
@property (nonatomic, copy) NSString *cassettesPath
属性:必需
引用存储记录磁带的路径。此路径将用于使用 cassettePath 生成具体磁带的路径。
注意:最好将该属性指向带有 .bundle
扩展名的包目录。
@property (nonatomic, copy) NSString *cassettePath
属性:必需
参考 cassette 存储或将要存储位置的路径(相对于 cassettesPath)。
注意: VCR 能够使用支持的一种文件类型序列化 cassette:属性列表(cassette path 应具有 plist
扩展名)和 JSON(cassette path 应具有 json
扩展名)。在省略扩展名的 cassette path 中,默认使用 JSON 序列化器。
@property (nonatomic, copy) id hostFilter
可以用来筛选用于记录/回放的请求数据的对象引用。该对象可以是包含允许的主机列表的数组或 YHVHostFilterBlock
块,它可以动态地决定是否应该记录/模拟回放请求。
示例
// Record only requests sent to apple.com
configuration.hostFilter = @[@"apple.com"];
// Record all requests which has been sent to httpbin service.
configuration.hostsFilter = ^BOOL (NSString *host) {
return [host rangeOfString:@"httpbin"].location != NSNotFound;
};
@property (nonatomic, nullable, copy) NSArray *matchers
注册的匹配器列表的引用,VCR 将使用这些匹配器来识别是否可以使用模拟请求而不是原始请求。
可用的匹配器
YHVMatcher.method
- 基于所使用请求数据的 HTTP 方法进行匹配。
如果两个请求都使用相同的 HTTP 请求方法(如GET
或POST
),则它们将匹配。YHVMatcher.uri
- 基于请求的完整 URI 进行匹配。
只有当两个请求都具有相同的 URI 字符串(包括:协议、主机、端口、路径和查询参数)时,它们才会匹配。YHVMatcher.scheme
- 基于 URI 协议进行匹配。
如果两个请求使用具有相同协议的 URI(如http
或https
),则它们将匹配。YHVMatcher.host
- 基于 URI 域进行匹配。
请求只有在使用的URI中具有相同的域名时才会匹配。YHVMatcher.port
- 根据URI的主机端口进行匹配。请求只有在使用的URI中具有相同的主机端口或者在URI中没有端口时才会匹配。YHVMatcher.path
- 根据URI的路径段进行匹配。
请求只有在使用的URI中具有相同的路径段时才会匹配。YHVMatcher.query
- 根据URI的查询段进行匹配。
请求只有在使用的URI中具有相同的查询段时才会匹配。YHVMatcher.headers
- 根据请求头部进行匹配。请求只有在二者有相同的头部字段集合和值时才会匹配。YHVMatcher.body
- 根据POST体进行匹配。
请求只有在它们都具有POST
HTTP方法和POST体时才会匹配。
@property (nonatomic, assign) YHVRecordMode recordMode
记录模式用于确定当前请求是否可以存储在盒带(cassette)上。
可用模式
YHVRecordOnce
- 请求只写入新盒带的模式。
当需要创建盒带并跟踪是否发送了异常请求时此模式很有用(在这种情况下,VCR将引发异常)。YHVRecordNew
- 请求将在当前播放头位置进行记录的模式。
当测试套件中添加了新测试用例并且需要相应地模拟它们的响应时,此模式很有用。在此模式下,将无法跟踪代码是否发送了意外请求。YHVRecordNone
- 不能执行任何写入的模式。
此模式完全保护盒带免受写入(类似于YHVRecordOnce
,允许写入初始盒带)。YHVRecordAll
- 任何请求都会被写入的模式。当插入盒带并设置此模式时,将移除其所有内容。当仿真内容过时,因为远程改变了输出格式或传输的信息时,此模式很有用。
@property (nonatomic, assign) YHVPlaybackMode playbackMode
回放模式用于确定如何将数据传递给URL加载系统。
请求的响应在盒带中不是单个条目,而是由以下内容组成:响应实例(NSHTTPURLResponse
)和响应体(NSData
或请求处理过程中出现的错误时的NSError
)。如果体内容太大,无法单包发送,盒带中可以有多个响应体条目。
有时卡式盒可能包含多个请求的桩(stub),它们随机(按照发送顺序)位于卡式盒的磁带上。因此,无法保证在响应体数据包之后将是相同请求的另一个数据包。在这种情况下,桩的回放由指定的模式控制。
可用模式
YHVChronologicalPlayback
- 默认模式,其中下一个记录场景只有在上一个场景完成之后才会播放。
在这个模式下,磁带上定位了多个请求的桩,只有在之前确认的桩接收到之后,才会播放下一个桩组件。这种方式是请求完成的自然方向,与记录时完成的顺序相同。YHVMomentaryPlayback
- 模式,记录的场景将以它们记录时的顺序播放,但完成后立即结束。
在这个模式下,只有在播放了上一个请求的桩组件后,才能播放下一个桩组件。
@property (nonatomic, copy) YHVPathFilterBlock pathFilter
关于允许在将请求URI路径部分存储为桩之前过滤掉敏感数据的块。
示例
/**
* In example below, we return path segment where any occurrence of our
* username replaced with 'bob'.
*/
configuration.pathFilter = ^NSString *(NSURLRequest *request) {
return [request.URI.path stringByReplacingOccurrencesOfString:@"<username>" withString:@"bob"];
};
@property (nonatomic, copy) id queryParametersFilter
关于可以在将其存储为桩之前过滤请求URI查询部分的敏感数据的对象的引用。
对象可以是实例为NSDictionary
,其中键代表查询参数的名称,值是原始数据替换。可以通过在字典中将值指定为[NSNull null]
来删除有值的查询字段。
对象还可以是允许动态更改查询参数的YHVQueryParametersFilterBlock
块。
示例
/**
* In example below, we remove 'token' query and replace 'signature' with
* own value which will be stored as stub.
*/
configuration.queryParametersFilter = @{ @"token": [NSNull null], @"signature": @"secret-signature" };
// This block is identical to filter configuration with NSDictionary above.
configuration.queryParametersFilter = ^(NSURLRequest *request, NSMutableDictionary *queryParameters) {
[queryParameters removeObjectForKey:@"token"];
queryParameters[@"signature"] = @"secret-signature";
};
@property (nonatomic, copy) id postBodyFilter
这是可以用于在将请求数据存入cassette之前从中过滤敏感数据的对象的引用。
该对象可以是名称作为键,原始数据替换作为值的NSDictionary
实例。可以通过在字典中将值指定为[NSNull null]
来移除带有值的头字段。NSDictionary
只能在与请求一起发送的application/json
或application/x-www-form-urlencoded
数据中使用。
该对象还可以是允许动态更改头字段的YHVPostBodyFilterBlock
块。
示例
/**
* In example below allow to remove 'fullName' and replace 'pwd' field in
* POST body represented by 'application/json' or
* 'application/x-www-form-urlencoded' content-type.
*/
configuration.postBodyFilter = @{ @"fullName": [NSNull null], @"pwd": @"pwd" };
/**
* In example below, we replace 'sender:alex' string in POST body with
* 'sender:bob' which will be part of stored stub.
*/
configuration.postBodyFilter = ^NSData * (NSURLRequest *request, NSData *body) {
NSString *message = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding];
message = [request.URI.path stringByReplacingOccurrencesOfString:@"sender:alex" withString:@"sender:bob"];
return [message dataUsingEncoding:NSUTF8StringEncoding];
};
@property (nonatomic, copy) id responseBodyFilter
这是可以用于在将请求数据存入cassette之前从中过滤敏感数据的对象的引用。
该对象可以是名称作为键,原始数据替换作为值的NSDictionary
实例。可以通过在字典中将值指定为[NSNull null]
来移除带有值的头字段。NSDictionary
只能在与请求一起发送的application/json
或application/x-www-form-urlencoded
数据中使用。
该对象还可以是允许动态更改头字段的YHVResponseBodyFilterBlock
块。
示例
/**
* In example below allow to remove 'token' from response body represented
* by 'application/json' or 'application/x-www-form-urlencoded' content-type.
*/
configuration.responseBodyFilter = @{ @"token": [NSNull null] };
/**
* In example below, we remove 'sender:bob' string from response body before
* it will be stored as stub.
*/
configuration.responseBodyFilter = ^NSData * (NSURLRequest *request, NSHTTPURLResponse *response, NSData *data) {
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
responseString = [request.URI.path stringByReplacingOccurrencesOfString:@"sender:bob" withString:@""];
return [responseString dataUsingEncoding:NSUTF8StringEncoding];
};
@property (nonatomic, copy) YHVBeforeRecordRequestBlock beforeRecordRequest
关于允许在将NSURLRequest
实例存储为卡式录音的占位符之前进行最终调整的块引用。
示例
/**
* In example below, we change used HTTP method.
*/
configuration.beforeRecordRequest = ^NSURLRequest *(NSURLRequest *request) {
NSMutableURLRequest *changedRequest = [request mutableCopy];
changedRequest.HTTPMethod = @"GET";
return changedRequest;
};
@property (nonatomic, copy) YHVBeforeRecordResponseBlock beforeRecordResponse
关于允许在将NSURLRequest
实例存储为卡式录音的占位符之前进行最终调整的块引用。
示例
/**
* In example below, we remove received service data from stub.
*/
configuration.beforeRecordRequest = ^NSArray * (NSURLRequest *request, NSHTTPURLResponse *response, NSData *data) {
return @[response];
};
API
VCR
属性
@property (class, nonatomic, readonly, strong) YHVCassette *cassette
当前插入到VCR的磁带的引用。
@property (class, nonatomic, readonly, strong) NSDictionary *matchers
注册的请求匹配器到它们的GCD块的映射的引用。
方法
+ (void)setupWithConfiguration:(void (^)(YHVConfiguration *)configuration)
配置共享VCR实例。此方法可以被多次调用以覆盖默认的VCR配置。
示例
NSString *uniquePath = [@[NSTemporaryDirectory(), [NSUUID UUID].UUIDString]
componentsJoinedByString:@"/"];
[YHVVCR setupWithConfiguration:^(YHVConfiguration *configuration) {
configuration.cassettesPath = [uniquePath stringByAppendingPathExtension:@"bundle"];
configuration.hostFilter = @[@"apple.com"];
}];
+ (YHVCassette *)insertCassetteWithPath:(NSString *)path
将新或现有盒式带插入VCR。此方法是使用预定义的 cassettePath 的 +insertCassetteWithConfigurationL:
简略方式。
返回插入盒式带的引用,可用于进一步在代码中使用。
示例
// Insert cassette restored from JSON.
YHVCassette *cassette = [YHVVCR insertCassetteWithPath:@"SearchStubCassette"];
+ (YHVCassette *)insertCassetteWithConfiguration:(void (^)(YHVConfiguration *configuration)))block
将新或现有盒式带插入VCR,并带有盒式带级别的配置。此方法可以覆盖由VCR提供的配置(它不会更改VCR中的配置)
示例
YHVCassette *cassette = [YHVVCR insertCassetteWithConfiguration:^(YHVConfiguration *configuration) {
configuration.cassettePath = @"SearchStubCassette";
configuration.playbackMode = YHVMomentaryPlayback;
}];
+ (void)ejectCassette
从VCR中弹出之前的插入盒式带。在移除盒式带后,将不再记录或模拟任何新请求。
示例
[YHVVCR insertCassetteWithPath:@"SearchStubCassette"];
// Work with stubbed data.
[YHVVCR ejectCassette];
+ (void)registerMatcher:(NSString *)identifier withBlock:(YHVMatcherBlock)block
使用指定的标识符注册新的匹配器块。匹配器用于检查磁带是否包含用户代码已发送的上传请求的模拟请求。
在传递 request
给匹配器之前,它将被传递到 beforeRecordRequest
和所有配置的过滤器。
示例
[YHVVCR registerMatcher:@"hostAndPort" withBlock:^BOOL (NSURLRequest *request, NSURLRequest *stubRequest) {
YHVMatcherBlock hostMatcher = YHVVCR.matchers[YHVMatchers.host];
YHVMatcherBlock portMatcher = YHVVCR.matchers[YHVMatchers.port];
return hostMatcher(request, stubRequest) && portMatcher(request, stubRequest);
}];
+ (void)unregisterMatcher:(NSString *)identifier
通过其标识符注销自定义匹配器。
此方法不能用于删除以下默认匹配器: YHVMatcher.method
、YHVMatcher.uri
、YHVMatcher.scheme
、YHVMatcher.host
、YHVMatcher.port
、YHVMatcher.path
、YHVMatcher.query
、YHVMatcher.headers
和 YHVMatcher.body
。
示例
// Remove previously added matcher.
[YHVVCR unregisterMatcher:@"hostAndPort"];
磁带
属性
@property (nonatomic, readonly, copy) YHVConfiguration *configuration
关于合并的配置对象(与 VCR 配置合并)的引用,该对象将用于处理请求和匹配数据。
@property (nonatomic, readonly, assign) NSUInteger playCount
包含完全模拟请求的数量,这些请求已提供响应和数据(数据任务完成数据加载并通过处理块报告)。
@property (nonatomic, readonly, assign) BOOL allPlayed
磁带是否已播放到磁带末尾。
@property (nonatomic, readonly, assign, getter = isNewCassette) BOOL newCassette
是否为新磁带。如果是新磁带,则记录限制的部分不适用。
@property (nonatomic, readonly, assign, getter = isWriteProtected) BOOL writeProtected
是否可以将新请求写入磁带。只有具有 YHVRecordOnce
记录模式和 YHVRecordNone
的现有磁带才可能导致此属性返回 YES。
@property (nonatomic, readonly, strong) NSArray<NSURLRequest *> *requests
对用于模拟的磁带存储数据的请求列表的引用。
@[property (nonatomic, readonly, strong) NSArray *responses
响应列表的引用,其中每个条目是一个嵌套数组,第一个元素是一个NSURLResponse实例,第二个是NSData或NSError(根据是否记录了请求成功或错误)。
XCTestCase
库具有辅助类(YHVTestCase
),默认执行一些额外任务,使用测试更加方便。
辅助类默认操作包括录制路径组合(测试套件名称将是bundle
的名称),包括录制名称生成(基于测试方法名称)。
属性
@[property (nonatomic, readonly, copy) NSString *cassettesPath
引用存储或将要记录的录制位置。如果已记录新的录制,可以从测试套件打印此值以找到存储bundle
的位置。
注意:如果已记录 Indigenous,应将 Bundle 存储并复制到Fixture
文件夹中。
@[property (nonatomic, readonly, copy) NSString *cassettePath
当前使用的录制完整路径的引用。
方法
YHVTestCase
子类将继承YHVTestCaseDelegate
协议的实现,并能动态调整VCR和cassette使用的配置。
- (BOOL)shouldSetupVCR
在包含测试的类中实现此方法,以确定是否应使用VCR。
示例
- (BOOL)shouldSetupVCR {
// Use VCR only in case if test case method contain 'Stubbed' in it's name.
return [self.name rangeOfString:@"Stubbed"].location != NSNotFound;
}
- (void)updateVCRConfigurationFromDefaultConfiguration:(YHVConfiguration *)configuration
此回调在传递给VCR的configuration
对象之前由YHVTestCase
调用,并与+setupWithConfiguration:
一同使用。这是修改配置的最后机会,因为这些配置才能完成测试用例的VCR配置。
示例
- (void)updateVCRConfigurationFromDefaultConfiguration:(YHVConfiguration *)configuration {
/**
* Record any requests from test case which contain 'OutdatedStub' in it's
* name (something changed and stubbed data should be reloaded).
*/
if ([self.name rangeOfString:@"OutdatedStub"].location != NSNotFound) {
configuration.recordMode = YHVRecordAll;
}
}
- (void)updateVCRConfigurationFromDefaultConfiguration:(YHVConfiguration *)configuration
此回调由 YHVTestCase
在将 configuration
对象传递给 VCR 的 +insertCassetteWithConfiguration:
之前使用。这是在测试用例的 VCR 配置完成之前修改磁带配置的最后机会。
示例
- (void)updateVCRConfigurationFromDefaultConfiguration:(YHVConfiguration *)configuration {
/**
* Change responses playback flow from chronological to momentary - in this
* mode when requested, stubbed data will be returned till data task will
* report completion.
*/
if ([self.name rangeOfString:@"Momentary"].location != NSNotFound) {
configuration.playbackMode = YHVMomentaryPlayback;
}
}