Skip to content

Commit 5359e8c

Browse files
committed
test RatingsFetcher
1 parent e20b949 commit 5359e8c

File tree

8 files changed

+119
-15
lines changed

8 files changed

+119
-15
lines changed

Conjugar.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
038E03182270AC01005CFC96 /* DeviceUtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038E03172270AC01005CFC96 /* DeviceUtilityTests.swift */; };
1414
038E031A22711B76005CFC96 /* UIViewControllerExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038E031922711B76005CFC96 /* UIViewControllerExtensionTests.swift */; };
1515
038E031C2271E239005CFC96 /* AnalyticsServiceableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038E031B2271E239005CFC96 /* AnalyticsServiceableTests.swift */; };
16+
038E031E22734800005CFC96 /* URLProtocolStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038E031D22734800005CFC96 /* URLProtocolStub.swift */; };
17+
038E03202273BF29005CFC96 /* RatingsFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038E031F2273BF29005CFC96 /* RatingsFetcherTests.swift */; };
1618
E10178021F3F753400F0BC97 /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10178011F3F753400F0BC97 /* UIViewExtensions.swift */; };
1719
E107B08B1EB66CE6004C5E91 /* verbs.xml in Resources */ = {isa = PBXBuildFile; fileRef = E107B08A1EB66CE6004C5E91 /* verbs.xml */; };
1820
E107B0921EB66F28004C5E91 /* VerbParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107B0911EB66F28004C5E91 /* VerbParser.swift */; };
@@ -153,6 +155,8 @@
153155
038E03172270AC01005CFC96 /* DeviceUtilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceUtilityTests.swift; sourceTree = "<group>"; };
154156
038E031922711B76005CFC96 /* UIViewControllerExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtensionTests.swift; sourceTree = "<group>"; };
155157
038E031B2271E239005CFC96 /* AnalyticsServiceableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsServiceableTests.swift; sourceTree = "<group>"; };
158+
038E031D22734800005CFC96 /* URLProtocolStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLProtocolStub.swift; sourceTree = "<group>"; };
159+
038E031F2273BF29005CFC96 /* RatingsFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingsFetcherTests.swift; sourceTree = "<group>"; };
156160
E10178011F3F753400F0BC97 /* UIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtensions.swift; sourceTree = "<group>"; };
157161
E107B08A1EB66CE6004C5E91 /* verbs.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = verbs.xml; sourceTree = "<group>"; };
158162
E107B0911EB66F28004C5E91 /* VerbParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerbParser.swift; sourceTree = "<group>"; };
@@ -356,6 +360,7 @@
356360
E1EC70F02198A10B00872787 /* UIViewControllerExtension.swift */,
357361
E1E092C221ED3C6900828C37 /* UserDefaultsGetterSetter.swift */,
358362
E18F7F371ECA7C9A00633200 /* Utterer.swift */,
363+
038E031D22734800005CFC96 /* URLProtocolStub.swift */,
359364
);
360365
name = Utils;
361366
sourceTree = "<group>";
@@ -410,6 +415,7 @@
410415
children = (
411416
E151223021A62AE9008EF307 /* ReviewPrompterTests.swift */,
412417
038E03122270A868005CFC96 /* IntExtensionTests.swift */,
418+
038E031F2273BF29005CFC96 /* RatingsFetcherTests.swift */,
413419
038E03142270A9FB005CFC96 /* TestGameCenterTests.swift */,
414420
038E031922711B76005CFC96 /* UIViewControllerExtensionTests.swift */,
415421
);
@@ -807,6 +813,7 @@
807813
E1A44314200D10D200C48857 /* ConjugationDataSource.swift in Sources */,
808814
E1171C9521ACC02200B06342 /* GameCenterable.swift in Sources */,
809815
E115CA391EEC962700DBBD44 /* VerbFamilies.swift in Sources */,
816+
038E031E22734800005CFC96 /* URLProtocolStub.swift in Sources */,
810817
E15E63F61F3A29890083DB4B /* Layout.swift in Sources */,
811818
E1F289A71F30C666006B3C3E /* SettingsView.swift in Sources */,
812819
E1F06AD81E8F05F300ADD2E1 /* AppDelegate.swift in Sources */,
@@ -842,6 +849,7 @@
842849
038E031C2271E239005CFC96 /* AnalyticsServiceableTests.swift in Sources */,
843850
E15D3BF61F533997003771BB /* MockNavigationC.swift in Sources */,
844851
E198F9021F5D79F100BAF553 /* VerbViewTests.swift in Sources */,
852+
038E03202273BF29005CFC96 /* RatingsFetcherTests.swift in Sources */,
845853
038E03152270A9FB005CFC96 /* TestGameCenterTests.swift in Sources */,
846854
E1F06AED1E8F05F400ADD2E1 /* MainTabBarVCTests.swift in Sources */,
847855
E15D3BF11F532F4E003771BB /* BrowseVerbsVCTests.swift in Sources */,

Conjugar/AppDelegate.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
3737
settings.difficulty = .easy
3838
}
3939

40-
mainTabBarVC = MainTabBarVC(settings: settings, quiz: Quiz(settings: settings, gameCenter: TestGameCenter(), shouldShuffle: false), analyticsService: TestAnalyticsService(), reviewPrompter: TestReviewPrompter(), gameCenter: TestGameCenter())
40+
mainTabBarVC = MainTabBarVC(settings: settings, quiz: Quiz(settings: settings, gameCenter: TestGameCenter(), shouldShuffle: false), analyticsService: TestAnalyticsService(), reviewPrompter: TestReviewPrompter(), gameCenter: TestGameCenter(), session: stubSession)
4141
} else {
4242
settings = Settings(getterSetter: UserDefaultsGetterSetter())
4343
#if targetEnvironment(simulator)
4444
let testGameCenter = TestGameCenter()
45-
mainTabBarVC = MainTabBarVC(settings: settings, quiz: Quiz(settings: settings, gameCenter: testGameCenter, shouldShuffle: true), analyticsService: TestAnalyticsService(), reviewPrompter: TestReviewPrompter(), gameCenter: testGameCenter)
45+
mainTabBarVC = MainTabBarVC(settings: settings, quiz: Quiz(settings: settings, gameCenter: testGameCenter, shouldShuffle: true), analyticsService: TestAnalyticsService(), reviewPrompter: TestReviewPrompter(), gameCenter: testGameCenter, session: stubSession)
4646
#else
47-
mainTabBarVC = MainTabBarVC(settings: settings, quiz: Quiz.shared, analyticsService: AWSAnalyticsService.shared, reviewPrompter: ReviewPrompter.shared, gameCenter: GameCenter.shared)
47+
mainTabBarVC = MainTabBarVC(settings: settings, quiz: Quiz.shared, analyticsService: AWSAnalyticsService.shared, reviewPrompter: ReviewPrompter.shared, gameCenter: GameCenter.shared, session: URLSession.shared)
4848
#endif
4949
}
5050

@@ -56,6 +56,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
5656
return true
5757
}
5858

59+
private var stubSession: URLSession {
60+
URLProtocolStub.testURLs = [RatingsFetcher.iTunesURL: RatingsFetcher.stubData()]
61+
let config = URLSessionConfiguration.ephemeral
62+
config.protocolClasses = [URLProtocolStub.self]
63+
return URLSession(configuration: config)
64+
}
65+
5966
private func configureStatusBar() {
6067
// I like blue and prefer not to override preferredStatusBarStyle in every UIViewController.
6168
let statusBarKey = "statusBar"

Conjugar/MainTabBarVC.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import UIKit
1111
class MainTabBarVC: UITabBarController {
1212
static let tabs = ["Browse", "Quiz", "Settings", "Info"]
1313

14-
convenience init(settings: Settings, quiz: Quiz, analyticsService: AnalyticsServiceable, reviewPrompter: ReviewPromptable, gameCenter: GameCenterable) {
14+
convenience init(settings: Settings, quiz: Quiz, analyticsService: AnalyticsServiceable, reviewPrompter: ReviewPromptable, gameCenter: GameCenterable, session: URLSession) {
1515
self.init(nibName: nil, bundle: nil)
1616
let browseVerbsNavC = UINavigationController(rootViewController: BrowseVerbsVC(settings: settings, analyticsService: analyticsService, reviewPrompter: reviewPrompter))
1717
browseVerbsNavC.tabBarItem = UITabBarItem(title: MainTabBarVC.tabs[0], image: UIImage(named: MainTabBarVC.tabs[0]), selectedImage: nil)
1818
let quizNavC = UINavigationController(rootViewController: QuizVC(settings: settings, quiz: quiz, analyticsService: analyticsService, gameCenter: gameCenter))
1919
quizNavC.tabBarItem = UITabBarItem(title: MainTabBarVC.tabs[1], image: UIImage(named: MainTabBarVC.tabs[1]), selectedImage: nil)
20-
let settingsNavC = UINavigationController(rootViewController: SettingsVC(settings: settings, analyticsService: analyticsService, gameCenter: gameCenter))
20+
let settingsNavC = UINavigationController(rootViewController: SettingsVC(settings: settings, analyticsService: analyticsService, gameCenter: gameCenter, session: session))
2121
settingsNavC.tabBarItem = UITabBarItem(title: MainTabBarVC.tabs[2], image: UIImage(named: MainTabBarVC.tabs[2]), selectedImage: nil)
2222
let browseInfoNavC = UINavigationController(rootViewController: BrowseInfoVC(settings: settings, analyticsService: analyticsService))
2323
browseInfoNavC.tabBarItem = UITabBarItem(title: MainTabBarVC.tabs[3], image: UIImage(named: MainTabBarVC.tabs[3]), selectedImage: nil)

Conjugar/RatingsFetcher.swift

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ import Foundation
1111
struct RatingsFetcher {
1212
static let iTunesID = "1236500467"
1313
static let errorMessage = "Fetching failed."
14-
15-
static func fetchDescription(completion: @escaping (String) -> ()) {
16-
guard let itunesUrl = URL(string: "https://itunes.apple.com/lookup?id=\(iTunesID)") else {
17-
return
14+
static var iTunesURL: URL {
15+
guard let url = URL(string: "https://itunes.apple.com/lookup?id=\(iTunesID)") else {
16+
fatalError("iTunes URL could not be initialized.")
1817
}
18+
return url
19+
}
1920

20-
let request = URLRequest(url: itunesUrl)
21+
static func fetchRatingsDescription(session: URLSession, completion: @escaping (String) -> ()) {
22+
let request = URLRequest(url: RatingsFetcher.iTunesURL)
2123

22-
let task = URLSession.shared.dataTask(with: request) { (responseData, _, error) in
24+
let task = session.dataTask(with: request) { (responseData, _, error) in
2325
if error != nil {
2426
completion(errorMessage)
2527
return
@@ -33,7 +35,6 @@ struct RatingsFetcher {
3335
completion(errorMessage)
3436
return
3537
}
36-
3738
let description: String
3839
let addYours = "Add yours!"
3940
switch ratingsCount {
@@ -50,4 +51,8 @@ struct RatingsFetcher {
5051

5152
task.resume()
5253
}
54+
55+
static func stubData(ratingsCount: Int = 42) -> Data {
56+
return Data("{ \"resultCount\":1, \"results\": [ { \"userRatingCountForCurrentVersion\": \(ratingsCount) } ] }".utf8)
57+
}
5358
}

Conjugar/SettingsVC.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class SettingsVC: UIViewController {
1212
private var settings: Settings?
1313
private var analyticsService: AnalyticsServiceable?
1414
private var gameCenter: GameCenterable?
15+
private var session: URLSession?
1516

1617
var settingsView: SettingsView {
1718
if let castedView = view as? SettingsView {
@@ -21,11 +22,12 @@ class SettingsVC: UIViewController {
2122
}
2223
}
2324

24-
convenience init(settings: Settings, analyticsService: AnalyticsServiceable, gameCenter: GameCenterable) {
25+
convenience init(settings: Settings, analyticsService: AnalyticsServiceable, gameCenter: GameCenterable, session: URLSession) {
2526
self.init()
2627
self.settings = settings
2728
self.analyticsService = analyticsService
2829
self.gameCenter = gameCenter
30+
self.session = session
2931
}
3032

3133
override func loadView() {
@@ -38,7 +40,10 @@ class SettingsVC: UIViewController {
3840
settingsView.gameCenterButton.addTarget(self, action: #selector(authenticate), for: .touchUpInside)
3941
settingsView.rateReviewButton.addTarget(self, action: #selector(rateReview), for: .touchUpInside)
4042

41-
RatingsFetcher.fetchDescription { description in
43+
guard let session = session else {
44+
return
45+
}
46+
RatingsFetcher.fetchRatingsDescription(session: session) { description in
4247
if description != RatingsFetcher.errorMessage {
4348
DispatchQueue.main.async {
4449
settingsView.showRatingsUI(description: description)

Conjugar/URLProtocolStub.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// URLProtocolStub.swift
3+
// Conjugar
4+
//
5+
// Created by Joshua Adams on 4/26/19.
6+
// Based on an article by Paul Hudson.
7+
8+
import Foundation
9+
10+
class URLProtocolStub: URLProtocol {
11+
static var testURLs = [URL?: Data]()
12+
13+
override class func canInit(with request: URLRequest) -> Bool {
14+
return true
15+
}
16+
17+
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
18+
return request
19+
}
20+
21+
override func startLoading() {
22+
if let url = request.url {
23+
if let data = URLProtocolStub.testURLs[url] {
24+
self.client?.urlProtocol(self, didLoad: data)
25+
}
26+
}
27+
self.client?.urlProtocolDidFinishLoading(self)
28+
}
29+
30+
override func stopLoading() { }
31+
}

ConjugarTests/Controllers/MainTabBarVCTests.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class MainTabBarVCTests: XCTestCase {
1313
func testMainTabBarVC() {
1414
let settings = Settings(getterSetter: DictionaryGetterSetter(dictionary: [:]))
1515
let testGameCenter = TestGameCenter()
16-
let mtbvc = MainTabBarVC(settings: settings, quiz: Quiz(settings: settings, gameCenter: testGameCenter, shouldShuffle: false), analyticsService: TestAnalyticsService(fire: {_ in}), reviewPrompter: TestReviewPrompter(), gameCenter: testGameCenter)
16+
let mtbvc = MainTabBarVC(settings: settings, quiz: Quiz(settings: settings, gameCenter: testGameCenter, shouldShuffle: false), analyticsService: TestAnalyticsService(fire: {_ in}), reviewPrompter: TestReviewPrompter(), gameCenter: testGameCenter, session: stubSession)
1717
XCTAssertNotNil(mtbvc)
1818
UIApplication.shared.keyWindow?.rootViewController = mtbvc
1919
XCTAssertNotNil(UIApplication.shared.keyWindow?.rootViewController)
@@ -57,4 +57,11 @@ class MainTabBarVCTests: XCTestCase {
5757
XCTFail("Fourth tab is not a UINavigationController.")
5858
}
5959
}
60+
61+
private var stubSession: URLSession {
62+
URLProtocolStub.testURLs = [RatingsFetcher.iTunesURL: RatingsFetcher.stubData()]
63+
let config = URLSessionConfiguration.ephemeral
64+
config.protocolClasses = [URLProtocolStub.self]
65+
return URLSession(configuration: config)
66+
}
6067
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// RatingsFetcherTests.swift
3+
// ConjugarTests
4+
//
5+
// Created by Joshua Adams on 4/26/19.
6+
// Copyright © 2019 Josh Adams. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import Conjugar
11+
12+
class RatingsFetcherTests: XCTestCase {
13+
func testNoReviews() {
14+
testDescription(count: 0, expectedDescription: "No one has rated this version of Conjugar. Be the first!")
15+
}
16+
17+
func testOneReview() {
18+
testDescription(count: 1, expectedDescription: "There is one rating for this version of Conjugar. Add yours!")
19+
}
20+
21+
func testManyReviews() {
22+
testDescription(count: 42, expectedDescription: "There are 42 ratings for this version of Conjugar. Add yours!")
23+
}
24+
25+
private func stubSession(ratingsCount: Int) -> URLSession {
26+
URLProtocolStub.testURLs = [RatingsFetcher.iTunesURL: RatingsFetcher.stubData(ratingsCount: ratingsCount)]
27+
let config = URLSessionConfiguration.ephemeral
28+
config.protocolClasses = [URLProtocolStub.self]
29+
return URLSession(configuration: config)
30+
}
31+
32+
private func testDescription(count: Int, expectedDescription: String) {
33+
let expection = expectation(description: "testDescription")
34+
RatingsFetcher.fetchRatingsDescription(session: stubSession(ratingsCount: count), completion: { actualDescription in
35+
XCTAssertEqual(actualDescription, expectedDescription)
36+
expection.fulfill()
37+
})
38+
let timeout: TimeInterval = 0.5
39+
wait(for: [expection], timeout: timeout)
40+
}
41+
}

0 commit comments

Comments
 (0)