我是相当新的雨燕和我目前正在写(使用XCTest)测试以下功能的单元测试:
func login(email: String, password: String) {
Auth.auth().signIn(withEmail: email, password: password) { (user, error) in
if let _error = error {
print(_error.localizedDescription)
} else {
self.performSegue(identifier: "loginSeg")
}
}
}
我的研究已确定,我需要为XCTest默认情况下这意味着它不会等待关闭完成运行同步执行使用XCTestExpectation功能(请纠正我,如果我错了)。
请告诉我扔我了我是如何测试的登录功能,因为它本身调用异步函数Auth.auth().signIn()
。我想测试签到是否成功。
道歉,如果这已经回答了,但我找不到直接解决这个问题的答案。
谢谢
更新:
从回答一些帮助,并进一步研究,我通过登录功能修改为使用转义关闭:
func login(email: String, password: String, completion: @escaping(Bool)->()) {
Auth.auth().signIn(withEmail: email, password: password) { (user, error) in
if let _error = error {
print(_error.localizedDescription)
completion(false)
} else {
self.performSegue(identifier: "loginSeg")
completion(true)
}
}
}
然后,我通过以下方式进行测试:
func testLoginSuccess() {
// other setup
let exp = expectation(description: "Check Login is successful")
let result = login.login(email: email, password: password) { (loginRes) in
loginResult = loginRes
exp.fulfill()
}
waitForExpectations(timeout: 10) { error in
if let error = error {
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
}
XCTAssertEqual(loginResult, true)
}
}
我的测试功能现在已经成功地测试登录功能。
希望这可以帮助别人,因为它给我留下难住了一段时间:)
以验证该呼叫是一个建筑的边界。单元测试是更快,更可靠,如果他们去到这样的边界,但不越过他们。我们可以通过分离协议背后的验证单身做到这一点。
我猜在signIn
的签名。不管是什么,复制并粘贴到一个协议:
protocol AuthProtocol {
func signIn(withEmail email: String, password: String, completion: @escaping (String, NSError?) -> Void)
}
这可以作为完整的验证界面的薄片,只取你想要的部分。这是接口分离原则的一个例子。
然后延伸验证,以符合该协议。它已经这样做,所以一致性是空的。
extension Auth: AuthProtocol {}
现在,在您的视图控制器,解压直接调用Auth.auth()
与默认值的属性:
var auth: AuthProtocol = Auth.auth()
谈谈这个属性,而不是直接Auth.auth()
:
auth.signIn(withEmail: email, …etc…
这引入了一个缝。测试可以用一个试谍照,记录auth
是如何被调用的实现替换signIn
。
final class SpyAuth: AuthProtocol {
private(set) var signInCallCount = 0
private(set) var signInArgsEmail: [String] = []
private(set) var signInArgsPassword: [String] = []
private(set) var signInArgsCompletion: [(String, Foundation.NSError?) -> Void] = []
func signIn(withEmail email: String, password: String, completion: @escaping (String, Foundation.NSError?) -> Void) {
signInCallCount += 1
signInArgsEmail.append(email)
signInArgsPassword.append(password)
signInArgsCompletion.append(completion)
}
}
测试可以注入SpyAuth到视图控制器,拦截一切通常会去验证。正如你所看到的,这包括完成关闭。我会写
print(_)
声明。最后,还有塞格斯的问题。苹果并没有给我们任何方式进行单元测试。作为一种变通方法,可以使一个部分模拟。事情是这样的:
final class TestableLoginViewController: LoginViewController {
private(set) var performSegueCallCount = 0
private(set) var performSegueArgsIdentifier: [String] = []
private(set) var performSegueArgsSender: [Any?] = []
override func performSegue(withIdentifier identifier: String, sender: Any?) {
performSegueCallCount += 1
performSegueArgsIdentifier.append(identifier)
performSegueArgsSender.append(sender)
}
}
有了这个,你可以拦截调用performSegue
。这不是理想的,因为它是一个遗留代码技术。但它应该让你开始。
final class LoginViewControllerTests: XCTestCase {
private var sut: TestableLoginViewController!
private var spyAuth: SpyAuth!
override func setUp() {
super.setUp()
sut = TestableLoginViewController()
spyAuth = SpyAuth()
sut.auth = spyAuth
}
override func tearDown() {
sut = nil
spyAuth = nil
super.tearDown()
}
func test_login_shouldCallAuthSignIn() {
sut.login(email: "EMAIL", password: "PASSWORD")
XCTAssertEqual(spyAuth.signInCallCount, 1, "call count")
XCTAssertEqual(spyAuth.signInArgsEmail.first, "EMAIL", "email")
XCTAssertEqual(spyAuth.signInArgsPassword.first, "PASSWORD", "password")
}
func test_login_withSuccess_shouldPerformSegue() {
sut.login(email: "EMAIL", password: "PASSWORD")
let completion = spyAuth.signInArgsCompletion.first
completion?("DUMMY", nil)
XCTAssertEqual(sut.performSegueCallCount, 1, "call count")
XCTAssertEqual(sut.performSegueArgsIdentifier.first, "loginSeg", "identifier")
let sender = sut.performSegueArgsSender.first
XCTAssertTrue(sender as? TestableLoginViewController === sut,
"Expected sender \(sut!), but was \(String(describing: sender))")
}
}
绝对没有异步这里,所以没有waitForExpectations
。我们捕捉的关闭,我们称之为封闭。
(这样的东西将在iOS unit testing book I'm currently writing深度覆盖。)
乔恩的答案是优秀的,我不能添加评论,所以我会在这里我的建议。对于那些谁拥有(因为任何原因)的静态/类函数,而不是一个单独或实例功能,这可以帮助你:
举例来说,如果你有Auth.signIn(withEmail: emai...
其中signIn
是静态函数。相反,使用:
var auth: AuthProtocol = Auth.auth()
采用:
var auth: AuthProtocol.Type = Auth.self
并指派它像这样
sut.auth = SpyAuth.self