🤡 Add tests for search.

Add Result and Commandant in test bundle.
This commit is contained in:
Ben Chatelain 2018-11-17 19:04:27 -07:00
parent 4938382104
commit 8a95fa04b8
7 changed files with 141 additions and 3 deletions

View file

@ -30,7 +30,6 @@ public struct SearchCommand: CommandProtocol {
}
public func run(_ options: Options) -> Result<(), MASError> {
guard let searchURLString = searchURLString(options.appName),
let searchJson = urlSession.requestSynchronousJSONWithURLString(searchURLString) as? [String: Any] else {
return .failure(.searchFailed)

View file

@ -8,7 +8,7 @@
import Foundation
public enum MASError: Error, CustomStringConvertible {
public enum MASError: Error, CustomStringConvertible, Equatable {
case notSignedIn
case signInDisabled
case signInFailed(error: NSError?)

View file

@ -45,7 +45,7 @@ public extension URLSession {
}
/// Return JSON synchronous from specified endpoint
public func requestSynchronousJSONWithURLString(_ requestString: String) -> Any? {
@objc public func requestSynchronousJSONWithURLString(_ requestString: String) -> Any? {
guard let url = URL(string: requestString) else {return nil}
var request = URLRequest(url:url)
request.httpMethod = "GET"

View file

@ -0,0 +1,9 @@
{
"resultCount":0,
"results": []
}

View file

@ -0,0 +1,75 @@
//
// MockURLSession.swift
// MasKitTests
//
// Created by Ben Chatelain on 11/13/18.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
/// Mock URLSession for testing.
class MockURLSession: URLSession {
private let responseFile: String
/// Initializes a mock URL session with a file for the response.
///
/// - Parameter responseFile: Name of file containing JSON response body.
init(responseFile: String) {
self.responseFile = responseFile
}
/// Override which returns JSON contents from a file.
///
/// - Parameter requestString: Ignored URL string
/// - Returns: Contents of responseFile
@objc override func requestSynchronousJSONWithURLString(_ requestString: String) -> Any? {
guard let fileURL = Bundle.jsonResponse(fileName: responseFile)
else { fatalError("Unable to load file \(responseFile)") }
do {
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject> {
return jsonResult
}
} catch {
print("Error parsing JSON: \(error)")
}
return nil
}
}
extension Bundle {
/// Locates a JSON response file from the test bundle.
///
/// - Parameter fileName: Name of file to locate.
/// - Returns: URL to file.
static func jsonResponse(fileName: String) -> URL? {
return Bundle(for: MockURLSession.self).fileURL(fileName: fileName)
}
/// Builds a URL for a file in the JSON directory of the current bundle.
///
/// - Parameter fileName: Name of file to locate.
/// - Returns: URL to file.
func fileURL(fileName: String) -> URL? {
guard let path = self.path(forResource: fileName.fileNameWithoutExtension, ofType: fileName.fileExtension, inDirectory: "JSON")
else { fatalError("Unable to load file \(fileName)") }
return URL(fileURLWithPath: path)
}
}
extension String {
/// Returns the file name before the extension.
var fileNameWithoutExtension: String {
return (self as NSString).deletingPathExtension
}
/// Returns the file extension.
var fileExtension: String {
return (self as NSString).pathExtension
}
}

View file

@ -7,12 +7,32 @@
//
@testable import MasKit
import Result
import Quick
import Nimble
class SearchSpec: QuickSpec {
override func spec() {
describe("search") {
context("for slack") {
it("succeeds") {
let search = SearchCommand(urlSession: MockURLSession(responseFile: "search_slack.json"))
let searchOptions = SearchOptions(appName: "slack", price: false)
let result = search.run(searchOptions)
expect(result).to(beSuccess())
}
}
context("for nonexistent") {
it("fails") {
let search = SearchCommand(urlSession: MockURLSession(responseFile: "search_nonexistent.json"))
let searchOptions = SearchOptions(appName: "nonexistent", price: false)
let result = search.run(searchOptions)
expect(result).to(beFailure { error in
expect(error) == .noSearchResultsFound
})
}
}
describe("url string") {
it("contains the app name") {
let appName = "myapp"
@ -40,3 +60,26 @@ class SearchSpec: QuickSpec {
}
}
}
/// Nimble predicate for result enum success case, no associated value
private func beSuccess() -> Predicate<Result<(), MASError>> {
return Predicate.define("be <success>") { expression, message in
if let actual = try expression.evaluate(),
case .success = actual {
return PredicateResult(status: .matches, message: message)
}
return PredicateResult(status: .fail, message: message)
}
}
/// Nimble predicate for result enum failure with associated error
private func beFailure(test: @escaping (MASError) -> Void = { _ in }) -> Predicate<Result<(), MASError>> {
return Predicate.define("be <failure>") { expression, message in
if let actual = try expression.evaluate(),
case let .failure(error) = actual {
test(error)
return PredicateResult(status: .matches, message: message)
}
return PredicateResult(status: .fail, message: message)
}
}

View file

@ -7,6 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
B537017321A0F85700538F78 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB4069213F4DDD0044E445 /* Result.framework */; };
B537017421A0F85B00538F78 /* Commandant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB406B213F4DDD0044E445 /* Commandant.framework */; };
B537017521A0F94000538F78 /* Result.framework in Copy Carthage Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB4069213F4DDD0044E445 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B537017621A0F94200538F78 /* Commandant.framework in Copy Carthage Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB406B213F4DDD0044E445 /* Commandant.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B5552928219A1BB900ACB4CA /* CommerceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F83213A62173EF75008BA8A0 /* CommerceKit.framework */; };
B5552929219A1BC700ACB4CA /* StoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F83213A52173EF75008BA8A0 /* StoreFoundation.framework */; };
B555292B219A1CB200ACB4CA /* MASErrorTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B555292A219A1CB200ACB4CA /* MASErrorTestCase.swift */; };
@ -16,6 +20,7 @@
B5552936219A23FF00ACB4CA /* Nimble.framework in Copy Carthage Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB406C213F4DDD0044E445 /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B5552937219A23FF00ACB4CA /* Quick.framework in Copy Carthage Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB406A213F4DDD0044E445 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B5793E29219BDD4800135B39 /* JSON in Resources */ = {isa = PBXBuildFile; fileRef = B5793E28219BDD4800135B39 /* JSON */; };
B5793E2B219BE0CD00135B39 /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5793E2A219BE0CD00135B39 /* MockURLSession.swift */; };
ED031A7C1B5127C00097692E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED031A7B1B5127C00097692E /* main.swift */; };
F83213892173D3E1008BA8A0 /* CKAccountStore.h in Headers */ = {isa = PBXBuildFile; fileRef = F8FB719B20F2EC4500F56FDC /* CKAccountStore.h */; };
F832138A2173D3E1008BA8A0 /* CKDownloadQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = F8FB719C20F2EC4500F56FDC /* CKDownloadQueue.h */; };
@ -106,8 +111,10 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
B537017621A0F94200538F78 /* Commandant.framework in Copy Carthage Frameworks */,
B5552936219A23FF00ACB4CA /* Nimble.framework in Copy Carthage Frameworks */,
B5552937219A23FF00ACB4CA /* Quick.framework in Copy Carthage Frameworks */,
B537017521A0F94000538F78 /* Result.framework in Copy Carthage Frameworks */,
);
name = "Copy Carthage Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -148,6 +155,7 @@
B555292A219A1CB200ACB4CA /* MASErrorTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MASErrorTestCase.swift; sourceTree = "<group>"; };
B555292C219A1FE700ACB4CA /* SearchSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSpec.swift; sourceTree = "<group>"; };
B5793E28219BDD4800135B39 /* JSON */ = {isa = PBXFileReference; lastKnownFileType = folder; path = JSON; sourceTree = "<group>"; };
B5793E2A219BE0CD00135B39 /* MockURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = "<group>"; };
ED031A781B5127C00097692E /* mas */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mas; sourceTree = BUILT_PRODUCTS_DIR; };
ED031A7B1B5127C00097692E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
ED0F237E1B87522400AE40CD /* Install.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Install.swift; sourceTree = "<group>"; };
@ -230,8 +238,10 @@
buildActionMask = 2147483647;
files = (
F8FB715B20F2B41400F56FDC /* MasKit.framework in Frameworks */,
B537017421A0F85B00538F78 /* Commandant.framework in Frameworks */,
B555292F219A219100ACB4CA /* Nimble.framework in Frameworks */,
B555292E219A218E00ACB4CA /* Quick.framework in Frameworks */,
B537017321A0F85700538F78 /* Result.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -346,6 +356,7 @@
F8FB716120F2B41400F56FDC /* Info.plist */,
B5793E28219BDD4800135B39 /* JSON */,
B555292A219A1CB200ACB4CA /* MASErrorTestCase.swift */,
B5793E2A219BE0CD00135B39 /* MockURLSession.swift */,
B555292C219A1FE700ACB4CA /* SearchSpec.swift */,
);
path = MasKitTests;
@ -609,6 +620,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5793E2B219BE0CD00135B39 /* MockURLSession.swift in Sources */,
B555292B219A1CB200ACB4CA /* MASErrorTestCase.swift in Sources */,
B555292D219A1FE700ACB4CA /* SearchSpec.swift in Sources */,
);