From 18c3c3c43468fce54d802e914d15d8302c249352 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 31 Dec 2019 11:40:56 -0500 Subject: [PATCH] More UI testing setup and API mocks --- Tusker.xcodeproj/project.pbxproj | 32 ++++++++ Tusker/LocalData.swift | 7 ++ .../MyProfileTableViewController.swift | 3 +- TuskerUITests/API Mocks/APIMocks.swift | 77 +++++++++++++++++++ .../API Mocks/DelegatingResponse.swift | 18 +++++ TuskerUITests/API Mocks/JSONResponse.swift | 18 +++++ TuskerUITests/MyProfileTests.swift | 26 +++++++ TuskerUITests/OnboardingTests.swift | 18 ++--- TuskerUITests/TuskerUITests.swift | 1 - 9 files changed, 185 insertions(+), 15 deletions(-) create mode 100644 TuskerUITests/API Mocks/APIMocks.swift create mode 100644 TuskerUITests/API Mocks/DelegatingResponse.swift create mode 100644 TuskerUITests/API Mocks/JSONResponse.swift create mode 100644 TuskerUITests/MyProfileTests.swift diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 752b7ee2..a71562d9 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -183,6 +183,10 @@ D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */; }; D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */; }; D6A3BC8F2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC8D2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift */; }; + D6A5BB2B23BAEF61003BF21D /* APIMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB2A23BAEF61003BF21D /* APIMocks.swift */; }; + D6A5BB2D23BBA9C4003BF21D /* MyProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB2C23BBA9C4003BF21D /* MyProfileTests.swift */; }; + D6A5BB2F23BBAC97003BF21D /* DelegatingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */; }; + D6A5BB3123BBAD87003BF21D /* JSONResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */; }; D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; }; D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; }; D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */; }; @@ -465,6 +469,10 @@ D6A3BC882321F79B00FD64D5 /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = ""; }; D6A3BC892321F79B00FD64D5 /* AccountTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountTableViewCell.xib; sourceTree = ""; }; D6A3BC8D2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActionAccountListTableViewController.swift; sourceTree = ""; }; + D6A5BB2A23BAEF61003BF21D /* APIMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIMocks.swift; sourceTree = ""; }; + D6A5BB2C23BBA9C4003BF21D /* MyProfileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfileTests.swift; sourceTree = ""; }; + D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatingResponse.swift; sourceTree = ""; }; + D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONResponse.swift; sourceTree = ""; }; D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = ""; }; D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = ""; }; D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMesasgeActivity.swift; sourceTree = ""; }; @@ -1127,6 +1135,23 @@ path = "Status Action Account List"; sourceTree = ""; }; + D6A5BB2623BAC88E003BF21D /* Preferences */ = { + isa = PBXGroup; + children = ( + ); + path = Preferences; + sourceTree = ""; + }; + D6A5BB2923BAEF51003BF21D /* API Mocks */ = { + isa = PBXGroup; + children = ( + D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */, + D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */, + D6A5BB2A23BAEF61003BF21D /* APIMocks.swift */, + ); + path = "API Mocks"; + sourceTree = ""; + }; D6AEBB3F2321640F00E5038B /* Activities */ = { isa = PBXGroup; children = ( @@ -1274,8 +1299,11 @@ D6D4DDEE212518A200E1C4BB /* TuskerUITests */ = { isa = PBXGroup; children = ( + D6A5BB2923BAEF51003BF21D /* API Mocks */, D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */, D65F613323AEAB6600F3CFD3 /* OnboardingTests.swift */, + D6A5BB2C23BBA9C4003BF21D /* MyProfileTests.swift */, + D6A5BB2623BAC88E003BF21D /* Preferences */, D6D4DDF1212518A200E1C4BB /* Info.plist */, ); path = TuskerUITests; @@ -1811,7 +1839,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D6A5BB3123BBAD87003BF21D /* JSONResponse.swift in Sources */, D65F613423AEAB6600F3CFD3 /* OnboardingTests.swift in Sources */, + D6A5BB2F23BBAC97003BF21D /* DelegatingResponse.swift in Sources */, + D6A5BB2D23BBA9C4003BF21D /* MyProfileTests.swift in Sources */, + D6A5BB2B23BAEF61003BF21D /* APIMocks.swift in Sources */, D6D4DDF0212518A200E1C4BB /* TuskerUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Tusker/LocalData.swift b/Tusker/LocalData.swift index b74872c4..f8ce0b10 100644 --- a/Tusker/LocalData.swift +++ b/Tusker/LocalData.swift @@ -17,6 +17,13 @@ class LocalData { private init() { if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING") { defaults = UserDefaults(suiteName: "\(Bundle.main.bundleIdentifier!).uitesting")! + if ProcessInfo.processInfo.environment.keys.contains("UI_TESTING_LOGIN") { + defaults.set(true, forKey: onboardingCompleteKey) + defaults.set(URL(string: "http://localhost:8080")!, forKey: instanceURLKey) + defaults.set("client_id", forKey: clientIDKey) + defaults.set("client_secret", forKey: clientSecretKey) + defaults.set("access_token", forKey: accessTokenKey) + } } else { defaults = UserDefaults() } diff --git a/Tusker/Screens/Profile/MyProfileTableViewController.swift b/Tusker/Screens/Profile/MyProfileTableViewController.swift index 7fdadc5c..3e58ac59 100644 --- a/Tusker/Screens/Profile/MyProfileTableViewController.swift +++ b/Tusker/Screens/Profile/MyProfileTableViewController.swift @@ -22,9 +22,8 @@ class MyProfileTableViewController: ProfileTableViewController { self.accountID = account.id ImageCache.avatars.get(account.avatar, completion: { (data) in - guard let data = data else { return } + guard let data = data, let image = UIImage(data: data) else { return } DispatchQueue.main.async { - let image = UIImage(data: data)! let size = CGSize(width: 30, height: 30) let tabBarImage = UIGraphicsImageRenderer(size: size).image { (_) in image.draw(in: CGRect(origin: .zero, size: size)) diff --git a/TuskerUITests/API Mocks/APIMocks.swift b/TuskerUITests/API Mocks/APIMocks.swift new file mode 100644 index 00000000..3fa5084c --- /dev/null +++ b/TuskerUITests/API Mocks/APIMocks.swift @@ -0,0 +1,77 @@ +// +// APIMocks.swift +// TuskerUITests +// +// Created by Shadowfacts on 12/30/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation +import Ambassador + +fileprivate let notFound = ["error": "Record not found"] + +extension Router { + func allRoutes() { + instanceRoutes() + accountRoutes() + timelineRoutes() + notificationRoutes() + } + + func instanceRoutes() { + self["/api/v1/instance"] = JSONResponse(handler: { (_) in + return [ + "description": "An instance description", + "max_toot_chars": 500, + "thumbnail": "http://localhost:8080/thumbnail.png", + "title": "Localhost", + "uri": "http://localhost:8080", + "version": "2.7.2", + "urls": [:] + ] + }) + } + + func accountRoutes() { + let selfAccount: [String: Any] = [ + "id": "1", + "username": "admin", + "acct": "admin", + "display_name": "Admin Account", + "locked": false, + "created_at": "2019-12-31T11:13:42.0Z", + "followers_count": 0, + "following_count": 0, + "statuses_count": 0, + "note": "My profile description.", + "url": "http://localhost:8080/users/admin", + "avatar": "http://localhost:8080/avatar/admin.jpg", + "avatar_static": "http://localhost:8080/avatar/admin.jpg", + "header": "http://localhost:8080/header/admin.jpg", + "header_static": "http://localhost:8080/header/admin.jpg", + "emojis": [] + ] + self["/api/v1/accounts/verify_credentials"] = JSONResponse(result: selfAccount) + self["/api/v1/accounts/\\d+/statuses"] = JSONResponse(result: []) + self["/api/v1/accounts/(\\d+)"] = DelegatingResponse { (env) in + let captures = env["ambassador.router_captures"] as! [String] + if captures[0] == "1" { + return JSONResponse(result: selfAccount) + } else { + return JSONResponse(statusCode: 404, statusMessage: "Not Found", result: notFound) + } + } + } + + func timelineRoutes() { + let emptyTimeline: [Any] = [] + self["/api/v1/timelines/home"] = JSONResponse(result: emptyTimeline) + self["/api/v1/timelines/public"] = JSONResponse(result: emptyTimeline) + } + + func notificationRoutes() { + let emptyTimeline: [Any] = [] + self["/api/v1/notifications"] = JSONResponse(result: emptyTimeline) + } +} diff --git a/TuskerUITests/API Mocks/DelegatingResponse.swift b/TuskerUITests/API Mocks/DelegatingResponse.swift new file mode 100644 index 00000000..87fe0e6e --- /dev/null +++ b/TuskerUITests/API Mocks/DelegatingResponse.swift @@ -0,0 +1,18 @@ +// +// DelegatingResponse.swift +// TuskerUITests +// +// Created by Shadowfacts on 12/31/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation +import Ambassador + +struct DelegatingResponse: WebApp { + let handler: (_ environ: [String: Any]) -> WebApp + + func app(_ environ: [String : Any], startResponse: @escaping ((String, [(String, String)]) -> Void), sendBody: @escaping ((Data) -> Void)) { + handler(environ).app(environ, startResponse: startResponse, sendBody: sendBody) + } +} diff --git a/TuskerUITests/API Mocks/JSONResponse.swift b/TuskerUITests/API Mocks/JSONResponse.swift new file mode 100644 index 00000000..d7dc2d00 --- /dev/null +++ b/TuskerUITests/API Mocks/JSONResponse.swift @@ -0,0 +1,18 @@ +// +// JSONResponse.swift +// TuskerUITests +// +// Created by Shadowfacts on 12/31/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import Foundation +import Ambassador + +extension JSONResponse { + init(statusCode: Int = 200, statusMessage: String = "OK", result: Any) { + self.init(statusCode: statusCode, statusMessage: statusMessage, handler: { (_) in + return result + }) + } +} diff --git a/TuskerUITests/MyProfileTests.swift b/TuskerUITests/MyProfileTests.swift new file mode 100644 index 00000000..585e3d2d --- /dev/null +++ b/TuskerUITests/MyProfileTests.swift @@ -0,0 +1,26 @@ +// +// MyProfileTests.swift +// TuskerUITests +// +// Created by Shadowfacts on 12/31/19. +// Copyright © 2019 Shadowfacts. All rights reserved. +// + +import XCTest + +class MyProfileTests: TuskerUITests { + + override func setUp() { + super.setUp() + + router.allRoutes() + + app.launchEnvironment["UI_TESTING_LOGIN"] = "true" + app.launch() + } + + func testExample() { + sleep(10000000) + } + +} diff --git a/TuskerUITests/OnboardingTests.swift b/TuskerUITests/OnboardingTests.swift index 1d697574..cf54c578 100644 --- a/TuskerUITests/OnboardingTests.swift +++ b/TuskerUITests/OnboardingTests.swift @@ -14,20 +14,14 @@ class OnboardingTests: TuskerUITests { override func setUp() { super.setUp() - router["/api/v1/instance"] = JSONResponse(handler: { (_) in - return [ - "description": "An instance description", - "max_toot_chars": 500, - "thumbnail": "http://localhost:8080/thumbnail.png", - "title": "Localhost", - "uri": "http://localhost:8080", - "version": "2.7.2", - "urls": [:] - ] - }) + router.instanceRoutes() + + app.launch() } + + // can't test logging in because there's no way of interacting with the safari VC that's used in the OAuth flow - func testExample() { + func testCustomInstanceAppears() { let searchField = app.searchFields.element searchField.tap() searchField.typeText("http://localhost:8080") diff --git a/TuskerUITests/TuskerUITests.swift b/TuskerUITests/TuskerUITests.swift index 73318acc..b353f68e 100644 --- a/TuskerUITests/TuskerUITests.swift +++ b/TuskerUITests/TuskerUITests.swift @@ -45,7 +45,6 @@ class TuskerUITests: XCTestCase { continueAfterFailure = false app = XCUIApplication() app.launchEnvironment["UI_TESTING"] = "true" - app.launch() } override func tearDown() {