IDPROD-2739 part 2: Screen routing tests (#457)

Added test coverage to `VerificationSheetFlowControllerTest` and `VerificationSheetControllerTest`
This commit is contained in:
Mel 2021-11-05 15:48:28 -07:00 committed by GitHub
parent 503486a3a9
commit 6cc01e7a98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 235 additions and 13 deletions

View File

@ -41,6 +41,7 @@
E6548EE62728AFB400F399B2 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548EE52728AFB400F399B2 /* TestConstants.swift */; };
E6548EE82728D39600F399B2 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548EE72728D39500F399B2 /* Async.swift */; };
E6548EF22729BDD700F399B2 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548EF12729BDD700F399B2 /* MockData.swift */; };
E6548F7F27339FB100F399B2 /* XCTestCase+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548F7E27339FB100F399B2 /* XCTestCase+Stripe.swift */; };
E6598C8126952BC500278740 /* STPLocalizationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6598C7E26952BC500278740 /* STPLocalizationUtils.swift */; };
E6598C8F269615E000278740 /* STPLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6598C8E269615E000278740 /* STPLocalizedString.swift */; };
E6598CAC2696177B00278740 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E6598C922696177B00278740 /* Localizable.strings */; };
@ -145,6 +146,7 @@
E6548EE52728AFB400F399B2 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = "<group>"; };
E6548EE72728D39500F399B2 /* Async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Async.swift; sourceTree = "<group>"; };
E6548EF12729BDD700F399B2 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = "<group>"; };
E6548F7E27339FB100F399B2 /* XCTestCase+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Stripe.swift"; sourceTree = "<group>"; };
E6598C7E26952BC500278740 /* STPLocalizationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPLocalizationUtils.swift; sourceTree = "<group>"; };
E6598C8E269615E000278740 /* STPLocalizedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPLocalizedString.swift; sourceTree = "<group>"; };
E6598C932696177B00278740 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -459,6 +461,7 @@
E6FB9BBB268EA95F000FDB4F /* Info.plist */,
E6FB9BBA268EA95F000FDB4F /* StripeCoreTestUtils.h */,
E6548EE52728AFB400F399B2 /* TestConstants.swift */,
E6548F7E27339FB100F399B2 /* XCTestCase+Stripe.swift */,
);
path = StripeCoreTestUtils;
sourceTree = "<group>";
@ -746,6 +749,7 @@
E61ADAA6270B92BD004ED998 /* UIView+StripeCoreTestingUtils.swift in Sources */,
E6CDC469269E81CD0020A962 /* MockAnalyticsClient.swift in Sources */,
E6548EDF2728AEBE00F399B2 /* APIStubbedTestCase.swift in Sources */,
E6548F7F27339FB100F399B2 /* XCTestCase+Stripe.swift in Sources */,
E6548EE62728AFB400F399B2 /* TestConstants.swift in Sources */,
E6548EF22729BDD700F399B2 /* MockData.swift in Sources */,
);

View File

@ -0,0 +1,19 @@
//
// XCTestCase+Stripe.swift
// StripeCoreTestUtils
//
// Created by Mel Ludowise on 11/3/21.
//
import XCTest
public extension XCTestCase {
func XCTAssertIs<T>(
_ item: Any,
_ t: T.Type,
file: StaticString = #filePath,
line: UInt = #line
) {
XCTAssert(item is T, "\(type(of: item)) is not type \(T.self)", file: file, line: line)
}
}

View File

@ -54,6 +54,7 @@
E6548F702731ED9800F399B2 /* VerificationSessionDataIDNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548F6F2731ED9800F399B2 /* VerificationSessionDataIDNumber.swift */; };
E6548F722731EE0E00F399B2 /* VerificationSessionDataName.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548F712731EE0E00F399B2 /* VerificationSessionDataName.swift */; };
E6548F792733628100F399B2 /* VerificationSheetAPIContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548F782733628100F399B2 /* VerificationSheetAPIContent.swift */; };
E6548F7D27339AC000F399B2 /* VerificationSheetFlowControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548F7C27339AC000F399B2 /* VerificationSheetFlowControllerTest.swift */; };
E6548F882734906600F399B2 /* MockIdentityAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548F872734906600F399B2 /* MockIdentityAPIClient.swift */; };
E6548F8A2734AEBA00F399B2 /* IdentityAPIClientTestMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6548F892734AEBA00F399B2 /* IdentityAPIClientTestMock.swift */; };
E6AF1ECA269FD7990091BE99 /* VerificationSheetAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6AF1EC5269FD7980091BE99 /* VerificationSheetAnalytics.swift */; };
@ -184,6 +185,7 @@
E6548F6F2731ED9800F399B2 /* VerificationSessionDataIDNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSessionDataIDNumber.swift; sourceTree = "<group>"; };
E6548F712731EE0E00F399B2 /* VerificationSessionDataName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSessionDataName.swift; sourceTree = "<group>"; };
E6548F782733628100F399B2 /* VerificationSheetAPIContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetAPIContent.swift; sourceTree = "<group>"; };
E6548F7C27339AC000F399B2 /* VerificationSheetFlowControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetFlowControllerTest.swift; sourceTree = "<group>"; };
E6548F872734906600F399B2 /* MockIdentityAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockIdentityAPIClient.swift; sourceTree = "<group>"; };
E6548F892734AEBA00F399B2 /* IdentityAPIClientTestMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityAPIClientTestMock.swift; sourceTree = "<group>"; };
E6AF1EC5269FD7980091BE99 /* VerificationSheetAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationSheetAnalytics.swift; sourceTree = "<group>"; };
@ -449,6 +451,7 @@
E6AF1EDE269FD9970091BE99 /* VerificationFlowWebViewTest.swift */,
E6AF1EDF269FD9970091BE99 /* VerificationSheetAnalyticsTest.swift */,
E6548EF72729BFC500F399B2 /* VerificationSheetControllerTest.swift */,
E6548F7C27339AC000F399B2 /* VerificationSheetFlowControllerTest.swift */,
);
path = Unit;
sourceTree = "<group>";
@ -781,6 +784,7 @@
E6AF1EE3269FD9970091BE99 /* IdentityVerificationSheetTest.swift in Sources */,
E6AF1EE2269FD9970091BE99 /* VerificationFlowWebViewControllerTest.swift in Sources */,
E6AF1EE4269FD9970091BE99 /* VerificationFlowWebViewTest.swift in Sources */,
E6548F7D27339AC000F399B2 /* VerificationSheetFlowControllerTest.swift in Sources */,
E6548EF82729BFC500F399B2 /* VerificationSheetControllerTest.swift in Sources */,
E6AF1EE1269FD9970091BE99 /* VerificationClientSecretTest.swift in Sources */,
E606937A270435FF00742859 /* IdentityElementsFactoryTest.swift in Sources */,

View File

@ -25,7 +25,7 @@ struct VerificationSheetAPIContent {
private(set) var staticContent: VerificationPage? = nil
/// Server response from the last time the user's data was saved
private var sessionData: VerificationSessionData? = nil
private(set) var sessionData: VerificationSessionData? = nil
private(set) var lastError: Error? = nil

View File

@ -16,8 +16,14 @@ final class VerificationSheetController {
let flowController = VerificationSheetFlowController()
let dataStore = VerificationSessionDataStore()
#if DEBUG
// Make apiContent settable from tests
/// Content returned from the API
var apiContent = VerificationSheetAPIContent()
#else
private(set) var apiContent = VerificationSheetAPIContent()
#endif
init(apiClient: IdentityAPIClient = STPAPIClient.shared,
addressSpecProvider: AddressSpecProvider = .shared) {

View File

@ -30,26 +30,40 @@ final class VerificationSheetFlowController {
let nextScreen = nextViewController(apiContent: apiContent, sheetController: sheetController)
navigationController.pushViewController(nextScreen, animated: true)
}
}
private extension VerificationSheetFlowController {
/// Instantiates and returns the next view controller to display in the flow.
func nextViewController(
apiContent: VerificationSheetAPIContent,
sheetController: VerificationSheetController
) -> UIViewController {
if apiContent.lastError != nil {
nextViewController(
missingRequirements: apiContent.missingRequirements,
staticContent: apiContent.staticContent,
requiredDataErrors: apiContent.requiredDataErrors,
lastError: apiContent.lastError,
sheetController: sheetController
)
}
func nextViewController(
missingRequirements: Set<VerificationPageRequirements.Missing>?,
staticContent: VerificationPage?,
requiredDataErrors: [VerificationSessionDataRequirementError],
lastError: Error?,
sheetController: VerificationSheetController
) -> UIViewController {
if lastError != nil {
// TODO(IDPROD-2749): return error screen
return LoadingViewController()
}
guard let missingRequirements = apiContent.missingRequirements,
let staticContent = apiContent.staticContent else {
guard let missingRequirements = missingRequirements,
let staticContent = staticContent else {
// TODO(IDPROD-2749): return error screen
return LoadingViewController()
}
guard apiContent.requiredDataErrors.isEmpty else {
guard requiredDataErrors.isEmpty else {
// TODO(IDPROD-2749): return error screen
return LoadingViewController()
}

View File

@ -11,10 +11,11 @@ import XCTest
final class VerificationSheetControllerTest: XCTestCase {
let mockSecret = "secret_123"
let mockStaticContent = try! VerificationPageMock.response200.make()
private var controller: VerificationSheetController!
private var mockAPIClient: IdentityAPIClientTestMock!
private var loadedExp: XCTestExpectation!
private var exp: XCTestExpectation!
override func setUp() {
super.setUp()
@ -22,7 +23,7 @@ final class VerificationSheetControllerTest: XCTestCase {
// Mock the api client
mockAPIClient = IdentityAPIClientTestMock()
controller = VerificationSheetController(apiClient: mockAPIClient)
loadedExp = expectation(description: "Controller finished loading")
exp = expectation(description: "Finished API call")
}
func testValidVerificationPageResponse() throws {
@ -30,7 +31,7 @@ final class VerificationSheetControllerTest: XCTestCase {
// Load
controller.load(clientSecret: mockSecret) {
self.loadedExp.fulfill()
self.exp.fulfill()
}
// Verify 1 request made with secret
@ -45,7 +46,7 @@ final class VerificationSheetControllerTest: XCTestCase {
mockAPIClient.verificationPage.respondToRequests(with: .success(mockResponse))
// Verify completion block is called
wait(for: [loadedExp], timeout: 1)
wait(for: [exp], timeout: 1)
// Verify response updated on controller
XCTAssertEqual(controller.apiContent.staticContent, mockResponse)
@ -57,17 +58,81 @@ final class VerificationSheetControllerTest: XCTestCase {
// Load
controller.load(clientSecret: mockSecret) {
self.loadedExp.fulfill()
self.exp.fulfill()
}
// Respond to request with error
mockAPIClient.verificationPage.respondToRequests(with: .failure(mockError))
// Verify completion block is called
wait(for: [loadedExp], timeout: 1)
wait(for: [exp], timeout: 1)
// Verify error updated on controller
XCTAssertNil(controller.apiContent.staticContent)
XCTAssertNotNil(controller.apiContent.lastError)
}
func testValidVerificationSessionDataResponse() throws {
let mockResponse = try VerificationSessionDataMock.response200.make()
// Mock that a VerificationPage response has already been received
controller.apiContent.setStaticContent(result: .success(mockStaticContent))
// Mock that the user has entered data
controller.dataStore.biometricConsent = true
// Save data
controller.saveData { mutatedApiContent in
XCTAssertEqual(mutatedApiContent.sessionData, mockResponse)
XCTAssertNil(mutatedApiContent.lastError)
self.exp.fulfill()
}
// Verify 1 request made with Id, EAK, and collected data
XCTAssertEqual(mockAPIClient.verificationSessionData.requestHistory.count, 1)
XCTAssertEqual(mockAPIClient.verificationSessionData.requestHistory.first?.id, mockStaticContent.id)
XCTAssertEqual(mockAPIClient.verificationSessionData.requestHistory.first?.ephemeralKey, mockStaticContent.ephemeralApiKey)
XCTAssertEqual(mockAPIClient.verificationSessionData.requestHistory.first?.data, controller.dataStore.toAPIModel)
// Verify response & error are nil until API responds to request
XCTAssertNil(controller.apiContent.sessionData)
XCTAssertNil(controller.apiContent.lastError)
// Respond to request with success
mockAPIClient.verificationSessionData.respondToRequests(with: .success(mockResponse))
// Verify completion block is called
wait(for: [exp], timeout: 1)
// Verify response updated on controller
XCTAssertEqual(controller.apiContent.sessionData, mockResponse)
XCTAssertNil(controller.apiContent.lastError)
}
func testErrorVerificationSessionDataResponse() throws {
let mockError = NSError(domain: "", code: 0, userInfo: nil)
// Mock that a VerificationPage response has already been received
controller.apiContent.setStaticContent(result: .success(mockStaticContent))
// Mock that the user has entered data
controller.dataStore.biometricConsent = true
// Save data
controller.saveData { mutatedApiContent in
XCTAssertNil(mutatedApiContent.sessionData)
XCTAssertNotNil(mutatedApiContent.lastError)
self.exp.fulfill()
}
// Respond to request with success
mockAPIClient.verificationSessionData.respondToRequests(with: .failure(mockError))
// Verify completion block is called
wait(for: [exp], timeout: 1)
// Verify response updated on controller
XCTAssertNil(controller.apiContent.sessionData)
XCTAssertNotNil(controller.apiContent.lastError)
}
}

View File

@ -0,0 +1,110 @@
//
// VerificationSheetFlowControllerTest.swift
// StripeIdentityTests
//
// Created by Mel Ludowise on 11/3/21.
//
import XCTest
import StripeCoreTestUtils
@testable import StripeIdentity
final class VerificationSheetFlowControllerTest: XCTestCase {
let flowController = VerificationSheetFlowController()
var mockVerificationPage = try! VerificationPageMock.response200.make()
let mockSheetController = VerificationSheetController()
func testNextViewControllerError() {
// TODO(IDPROD-2749): Test against an Error VC instead of Loading
// API error
XCTAssertIs(flowController.nextViewController(
missingRequirements: [.biometricConsent],
staticContent: mockVerificationPage,
requiredDataErrors: [],
lastError: NSError(domain: "", code: 0, userInfo: nil),
sheetController: mockSheetController
), LoadingViewController.self)
// No requirements
XCTAssertIs(flowController.nextViewController(
missingRequirements: nil,
staticContent: mockVerificationPage,
requiredDataErrors: [],
lastError: nil,
sheetController: mockSheetController
), LoadingViewController.self)
// No staticContent
XCTAssertIs(flowController.nextViewController(
missingRequirements: [.biometricConsent],
staticContent: nil,
requiredDataErrors: [],
lastError: nil,
sheetController: mockSheetController
), LoadingViewController.self)
// requiredDataErrors
XCTAssertIs(flowController.nextViewController(
missingRequirements: [.biometricConsent],
staticContent: mockVerificationPage,
requiredDataErrors: [.init(
code: .consentDeclined,
requirement: .biometricConsent,
title: "",
body: "",
buttonText: "",
_allResponseFieldsStorage: nil
)],
lastError: nil,
sheetController: mockSheetController
), LoadingViewController.self)
}
func testNextViewControllerSuccess() {
// TODO(IDPROD-2759): Test against Success VC instead of Loading
XCTAssertIs(nextViewController(
missingRequirements: []
), LoadingViewController.self)
}
func testNextViewControllerBiometricConsent() {
XCTAssertIs(nextViewController(
missingRequirements: [.biometricConsent]
), BiometricConsentViewController.self)
}
func testNextViewControllerIndividualFields() {
XCTAssertIs(nextViewController(
missingRequirements: [.address]
), IndividualViewController.self)
XCTAssertIs(nextViewController(
missingRequirements: [.dob]
), IndividualViewController.self)
XCTAssertIs(nextViewController(
missingRequirements: [.email]
), IndividualViewController.self)
XCTAssertIs(nextViewController(
missingRequirements: [.idNumber]
), IndividualViewController.self)
XCTAssertIs(nextViewController(
missingRequirements: [.name]
), IndividualViewController.self)
XCTAssertIs(nextViewController(
missingRequirements: [.phoneNumber]
), IndividualViewController.self)
}
}
private extension VerificationSheetFlowControllerTest {
func nextViewController(missingRequirements: Set<VerificationPageRequirements.Missing>) -> UIViewController {
return flowController.nextViewController(
missingRequirements: missingRequirements,
staticContent: mockVerificationPage,
requiredDataErrors: [],
lastError: nil,
sheetController: mockSheetController
)
}
}