模拟者是用Swift编写的库,可以使用自定义URLProtocol
模拟数据请求。
脱机运行所有数据请求单元测试?
URLSession
Alamofire
之类的流行框架单位测试是为Mocker
编写的,可以帮助您查看其工作原理。
嘲笑者将自动激活默认URL加载系统(如URLSession.shared
。在注册了第一个Mock
后共享。
为了使其与您的自定义URLSession
一起使用,需要注册MockingURLProtocol
:
let configuration = URLSessionConfiguration . default
configuration . protocolClasses = [ MockingURLProtocol . self ]
let urlSession = URLSession ( configuration : configuration )
就像在自定义URLSession
上注册一样非常相似。
let configuration = URLSessionConfiguration . af . default
configuration . protocolClasses = [ MockingURLProtocol . self ]
let sessionManager = Alamofire . Session ( configuration : configuration )
建议创建一个可以访问所有模拟数据的类。可以在此项目的单元测试中找到一个例子:
public final class MockedData {
public static let botAvatarImageResponseHead : Data = try ! Data ( contentsOf : Bundle ( for : MockedData . self ) . url ( forResource : " Resources/Responses/bot-avatar-image-head " , withExtension : " data " ) ! )
public static let botAvatarImageFileUrl : URL = Bundle ( for : MockedData . self ) . url ( forResource : " wetransfer_bot_avater " , withExtension : " png " ) !
public static let exampleJSON : URL = Bundle ( for : MockedData . self ) . url ( forResource : " Resources/JSON Files/example " , withExtension : " json " ) !
}
let originalURL = URL ( string : " https://www.wetransfer.com/example.json " ) !
let mock = Mock ( url : originalURL , contentType : . json , statusCode : 200 , data : [
. get : try ! Data ( contentsOf : MockedData . exampleJSON ) // Data containing the JSON response
] )
mock . register ( )
URLSession . shared . dataTask ( with : originalURL ) { ( data , response , error ) in
guard let data = data , let jsonDictionary = ( try ? JSONSerialization . jsonObject ( with : data , options : [ ] ) ) as? [ String : Any ] else {
return
}
// jsonDictionary contains your JSON sample file data
// ..
} . resume ( )
let originalURL = URL ( string : " https://www.wetransfer.com/api/foobar " ) !
var request = URLRequest ( url : originalURL )
request . httpMethod = " PUT "
let mock = Mock ( request : request , statusCode : 204 )
mock . register ( )
URLSession . shared . dataTask ( with : originalURL ) { ( data , response , error ) in
// ....
} . resume ( )
某些URL(例如身份验证URL)在查询中包含时间戳或UUID。要模拟这些,您可以忽略某个URL的查询:
/// Would transform to "https://www.example.com/api/authentication" for example.
let originalURL = URL ( string : " https://www.example.com/api/authentication?oauth_timestamp=151817037 " ) !
let mock = Mock ( url : originalURL , ignoreQuery : true , contentType : . json , statusCode : 200 , data : [
. get : try ! Data ( contentsOf : MockedData . exampleJSON ) // Data containing the JSON response
] )
mock . register ( )
URLSession . shared . dataTask ( with : originalURL ) { ( data , response , error ) in
guard let data = data , let jsonDictionary = ( try ? JSONSerialization . jsonObject ( with : data , options : [ ] ) ) as? [ String : Any ] else {
return
}
// jsonDictionary contains your JSON sample file data
// ..
} . resume ( )
let imageURL = URL ( string : " https://www.wetransfer.com/sample-image.png " ) !
Mock ( fileExtensions : " png " , contentType : . imagePNG , statusCode : 200 , data : [
. get : try ! Data ( contentsOf : MockedData . botAvatarImageFileUrl )
] ) . register ( )
URLSession . shared . dataTask ( with : imageURL ) { ( data , response , error ) in
let botAvatarImage : UIImage = UIImage ( data : data! ) ! // This is the image from your resources.
} . resume ( )
let exampleURL = URL ( string : " https://www.wetransfer.com/api/endpoint " ) !
Mock ( url : exampleURL , contentType : . json , statusCode : 200 , data : [
. head : try ! Data ( contentsOf : MockedData . headResponse ) ,
. get : try ! Data ( contentsOf : MockedData . exampleJSON )
] ) . register ( )
URLSession . shared . dataTask ( with : exampleURL ) { ( data , response , error ) in
// data is your mocked data
} . resume ( )
除了在静态DataType
实现中已经构建的构建外,还可以创建自定义的构建,将其用作Content-Type
标头键的值。
let xmlURL = URL ( string : " https://www.wetransfer.com/sample-xml.xml " ) !
Mock ( fileExtensions : " png " , contentType : . init ( name : " xml " , headerValue : " text/xml " ) , statusCode : 200 , data : [
. get : try ! Data ( contentsOf : MockedData . sampleXML )
] ) . register ( )
URLSession . shared . dataTask ( with : xmlURL ) { ( data , response , error ) in
let sampleXML : Data = data // This is the xml from your resources.
} . resume (
有时您想测试请求的取消是否有效。在这种情况下,模拟的请求不应立即完成,您需要延迟。这可以轻松添加:
let exampleURL = URL ( string : " https://www.wetransfer.com/api/endpoint " ) !
var mock = Mock ( url : exampleURL , contentType : . json , statusCode : 200 , data : [
. head : try ! Data ( contentsOf : MockedData . headResponse ) ,
. get : try ! Data ( contentsOf : MockedData . exampleJSON )
] )
mock . delay = DispatchTimeInterval . seconds ( 5 )
mock . register ( )
有时您想模拟短URL或其他重定向URL。通过保存响应并嘲笑重定向位置,这是可能的,可以在响应中找到:
Date: Tue, 10 Oct 2017 07:28:33 GMT
Location: https://wetransfer.com/redirect
通过为短URL和重定向URL创建模拟,您可以模拟重定向并测试此行为:
let urlWhichRedirects : URL = URL ( string : " https://we.tl/redirect " ) !
Mock ( url : urlWhichRedirects , contentType : . html , statusCode : 200 , data : [ . get : try ! Data ( contentsOf : MockedData . redirectGET ) ] ) . register ( )
Mock ( url : URL ( string : " https://wetransfer.com/redirect " ) ! , contentType : . json , statusCode : 200 , data : [ . get : try ! Data ( contentsOf : MockedData . exampleJSON ) ] ) . register ( )
当嘲笑者在注册时默认情况下捕获所有URL时,您可能最终会在不需要模拟请求的情况下抛出fatalError
。在这种情况下,您可以忽略URL:
let ignoredURL = URL ( string : " https://www.wetransfer.com " ) !
// Ignore any requests that exactly match the URL
Mocker . ignore ( ignoredURL )
// Ignore any requests that match the URL, with any query parameters
// e.g. https://www.wetransfer.com?foo=bar would be ignored
Mocker . ignore ( ignoredURL , matchType : . ignoreQuery )
// Ignore any requests that begin with the URL
// e.g. https://www.wetransfer.com/api/v1 would be ignored
Mocker . ignore ( ignoredURL , matchType : . prefix )
但是,如果您需要嘲笑者仅捕获模拟的URL并忽略其他所有URL,则可以将mode
属性设置为.optin
。
Mocker . mode = . optin
如果要回到原始模式,则只需将其设置为.optout
即可。
Mocker . mode = . optout
您可以请求Mock
以返回错误,从而测试错误处理。
Mock ( url : originalURL , contentType : . json , statusCode : 500 , data : [ . get : Data ( ) ] ,
requestError : TestExampleError . example ) . register ( )
URLSession . shared . dataTask ( with : originalURL ) { ( data , urlresponse , err ) in
XCTAssertNil ( data )
XCTAssertNil ( urlresponse )
XCTAssertNotNil ( err )
if let err = err {
// there's not a particularly elegant way to verify an instance
// of an error, but this is a convenient workaround for testing
// purposes
XCTAssertEqual ( " example " , String ( describing : err ) )
}
expectation . fulfill ( )
} . resume ( )
您可以在Mock
回调上注册,以使测试更容易。
var mock = Mock ( url : request . url! , contentType : . json , statusCode : 200 , data : [ . post : Data ( ) ] )
mock . onRequestHandler = OnRequestHandler ( httpBodyType : [ [ String : String ] ] . self , callback : { request , postBodyArguments in
XCTAssertEqual ( request . url , mock . request . url )
XCTAssertEqual ( expectedParameters , postBodyArguments )
onRequestExpectation . fulfill ( )
} )
mock . completion = {
endpointIsCalledExpectation . fulfill ( )
}
mock . register ( )
您还可以onRequest
completion
:
var mock = Mock ( url : url , contentType : . json , statusCode : 200 , data : [ . get : Data ( ) ] )
let requestExpectation = expectationForRequestingMock ( & mock )
let completionExpectation = expectationForCompletingMock ( & mock )
mock . register ( )
URLSession . shared . dataTask ( with : URLRequest ( url : url ) ) . resume ( )
wait ( for : [ requestExpectation , completionExpectation ] , timeout : 2.0 )
您可以清除所有注册的模拟:
Mocker . removeAll ( )
迦太基是一个分散的依赖管理器,可建立您的依赖关系并为您提供二进制框架。
您可以使用以下命令使用Homebrew安装迦太基:
$ brew update
$ brew install carthage
要使用Carthage将模拟器集成到您的Xcode项目中,请在您的Cartfile
中指定它:
github "WeTransfer/Mocker" ~> 3.0.0
运行carthage update
以构建框架并将构建Mocker.framework
拖动到Xcode项目中。
Swift软件包管理器是管理Swift代码分布的工具。它与Swift Build System集成在一起,以自动化下载,编译和链接依赖项的过程。
将模拟器作为软件包添加到您的Package.swift
文件,然后将其指定为您希望使用的目标的依赖性。
import PackageDescription
let package = Package (
name : " MyProject " ,
platforms : [
. macOS ( . v10_15 )
] ,
dependencies : [
. package ( url : " https://github.com/WeTransfer/Mocker.git " , . upToNextMajor ( from : " 3.0.0 " ) )
] ,
targets : [
. target (
name : " MyProject " ,
dependencies : [ " Mocker " ] ) ,
. testTarget (
name : " MyProjectTests " ,
dependencies : [ " MyProject " ] ) ,
]
)
要将模拟器作为XCode项目的依赖项添加,请选择“文件> swift watchages”>“添加软件包依赖关系”并输入存储库URL。
如果收到以下错误:找不到自动链接库xctest和xctestswiftsupport ,请在build选项下设置以下属性,从no到yes。
enable_testing_search_path to是
如果您不希望使用上述任何依赖管理者,则可以手动将模拟器集成到您的项目中。
打开终端, cd
到您的顶级项目目录中,并运行以下命令“如果”您的项目未作为git存储库初始化:
$ git init
通过运行以下命令,将模拟器添加为git subsodule:
$ git submodule add https://github.com/WeTransfer/Mocker.git
打开新的Mocker
文件夹,然后将Mocker.xcodeproj
拖到您应用程序Xcode项目的项目导航器中。
它应该嵌套在您应用程序的蓝色项目图标下方。它是否在所有其他Xcode组之上或下方都无关紧要。
在项目导航器中选择模拟器Mocker.xcodeproj
,然后验证部署目标与您的应用程序目标相匹配。
接下来,在项目导航器(蓝色项目图标)中选择您的应用程序项目以导航到目标配置窗口,然后在侧边栏中的“目标”标题下选择应用程序目标。
在该窗口顶部的标签栏中,打开“常规”面板。
单击“嵌入二进制文件”部分下的+
按钮。
选择Mocker.framework
。
就是这样!
Mocker.framework
是自动添加的,作为目标依赖关系,链接的框架和嵌入式框架在复制文件构建阶段中,这就是您在模拟器和设备上构建的全部。
有关更改列表,请参见ChangElog.md。
模拟器可根据麻省理工学院许可证提供。有关更多信息,请参见许可证文件。