Last active
March 3, 2017 19:10
-
-
Save msanders/84c994c33dade469ea7b7019fc5fe507 to your computer and use it in GitHub Desktop.
ArchivableSpec.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ArbitraryExtensions.swift | |
// InstaShopper | |
// | |
// Created by Michael Sanders on 6/18/16. | |
// Copyright © 2016 Instacart. All rights reserved. | |
// | |
import Curry | |
import Mockingjay | |
import SwiftCheck | |
extension HTTPMethod { | |
static var allValues: [HTTPMethod] { | |
return [GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD] | |
} | |
} | |
extension HTTPMethod: Arbitrary { | |
public static var arbitrary: Gen<HTTPMethod> { | |
return Gen<HTTPMethod>.fromElementsOf(self.allValues) | |
} | |
} | |
extension NSData { | |
public static var arbitrary: Gen<NSData> { | |
return String.arbitrary.map { $0.dataUsingEncoding($0.fastestEncoding) }.suchThat { | |
$0 != nil | |
}.map { $0! } | |
} | |
} | |
// From https://github.com/typelift/SwiftCheck/blob/master/Tutorial.playground/Contents.swift | |
struct ArbitraryDate: Arbitrary { | |
let getDate: NSDate | |
static var arbitrary: Gen<ArbitraryDate> { | |
return Gen.oneOf([ | |
Gen.pure(NSDate()), | |
Gen.pure(NSDate.distantFuture()), | |
Gen.pure(NSDate.distantPast()), | |
NSDate.init <^> NSTimeInterval.arbitrary, | |
]).map(ArbitraryDate.init) | |
} | |
} | |
extension Request { | |
init(endpoint: String, method: HTTPMethod, timestamp: NSDate, | |
parameters: [String: AnyObject]?, attachments: [RequestFileData]?) { | |
self.init(endpoint: endpoint, | |
method: method.description, | |
timestamp: timestamp, | |
parameters: parameters, | |
attachments: attachments) | |
} | |
} | |
extension RequestFileData: Arbitrary { | |
static var arbitrary: Gen<RequestFileData> { | |
return curry(RequestFileData.init) <^> NSData.arbitrary <*> String.arbitrary <*> | |
String.arbitrary <*> String.arbitrary | |
} | |
} | |
extension Request: Arbitrary { | |
static var arbitrary: Gen<Request> { | |
// Constrain size so tests don't take forever. | |
let arbitraryData = (0...10).map([RequestFileData].arbitrary.resize) | |
let arbitraryDictionary = (0...10).map { size in | |
[String: String].arbitrary.resize(size).map { $0 as [String: AnyObject] } | |
} | |
return curry(Request.init) <^> String.arbitraryFileName <*> HTTPMethod.arbitrary <*> | |
ArbitraryDate.arbitrary.map { $0.getDate } <*> | |
Gen.optionalOneOf(arbitraryDictionary) <*> | |
Gen.optionalOneOf(arbitraryData) | |
} | |
} | |
extension String { | |
static let arbitraryFileName: Gen<String> = Gen<Character>.oneOf([ | |
Generators.upper, | |
Generators.lower, | |
Generators.numeric, | |
Generators.special, | |
]).proliferateNonEmpty.map(String.init).suchThat { $0.isValidFileName } | |
} | |
extension Gen { | |
static func optionalOneOf<S: CollectionType where S.Generator.Element == Gen<A>, | |
S.Index: protocol<RandomType, | |
BidirectionalIndexType>>(sequence: S) -> SwiftCheck.Gen<Optional<A>> { | |
let optionalSequence = sequence as? [Gen<Optional<A>>] ?? [] | |
return Gen<Optional<A>>.oneOf(optionalSequence + [Gen<Optional<A>>.pure(nil)]) | |
} | |
} | |
private struct Generators { | |
// From https://github.com/typelift/SwiftCheck/blob/master/Tests/ComplexSpec.swift | |
static let upper: Gen<Character>= .fromElementsIn("A"..."Z") | |
static let lower: Gen<Character> = .fromElementsIn("a"..."z") | |
static let numeric: Gen<Character> = .fromElementsIn("0"..."9") | |
static let special: Gen<Character> = .fromElementsOf(["!", "#", "$", "%", "&", | |
"'", "*", "+", "-", "/", | |
"=", "?", "^", "_", "`", | |
"{", "|", "}", "~", "."]) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ArchivableSpec.swift | |
// InstaShopper | |
// | |
// Created by Michael Sanders on 6/1/16. | |
// Copyright © 2016 Instacart. All rights reserved. | |
// | |
import Curry | |
import Mapper | |
import Nimble | |
import Quick | |
import SwiftCheck | |
final class SharedArchivableSpec<Model: protocol<Archivable, Arbitrary, Equatable>> { | |
func sharedExamples() { | |
it("should archive single model") { | |
property("model is archivable") <- forAll { (model: Model) in | |
let data = try model.encode() | |
let newModel = try Model.decode(data) | |
return newModel == model | |
} | |
} | |
it("should archive a collection of models") { | |
property("collection is archivable") <- forAll { (models: ArrayOf<Model>) in | |
let data = try models.getArray.encode() | |
let newModels = try Model.decodeArray(data) | |
return newModels == models.getArray | |
} | |
} | |
it ("should error out if invalid model data was saved") { | |
guard let data = "invalid".dataUsingEncoding(NSUTF8StringEncoding) else { | |
fail("Failed encoding UTF-8 string") | |
return | |
} | |
expect { try Model.decode(data) }.to(throwError()) | |
} | |
it ("should error out if invalid array data was saved") { | |
guard let data = "invalid".dataUsingEncoding(NSUTF8StringEncoding) else { | |
fail("Failed encoding UTF-8 string") | |
return | |
} | |
expect { try Model.decodeArray(data) }.to(throwError()) | |
} | |
} | |
} | |
final class TestArchivableSpec: QuickSpec { | |
override func spec() { | |
SharedArchivableSpec<TestArchivable>().sharedExamples() | |
} | |
} | |
final class RequestSpec: QuickSpec { | |
override func spec() { | |
SharedArchivableSpec<Request>().sharedExamples() | |
} | |
} | |
private struct TestArchivable { | |
let propertyA: String | |
let propertyB: Int | |
let propertyC: NSTimeInterval | |
let propertyD: Int? | |
} | |
extension TestArchivable: Archivable { | |
init(map: Mapper) throws { | |
propertyA = try map.from("propertyA") | |
propertyB = try map.from("propertyB") | |
propertyC = try map.from("propertyC") | |
propertyD = map.optionalFrom("propertyD") | |
} | |
func encode(archive: Archiver) { | |
archive.encode("propertyA", value: propertyA) | |
archive.encode("propertyB", value: propertyB) | |
archive.encode("propertyC", value: propertyC) | |
archive.encode("propertyD", value: propertyD) | |
} | |
} | |
extension TestArchivable: Equatable {} | |
private func == (lhs: TestArchivable, rhs: TestArchivable) -> Bool { | |
return lhs.propertyA == rhs.propertyA && lhs.propertyB == rhs.propertyB && | |
lhs.propertyC == rhs.propertyC && lhs.propertyD == rhs.propertyD | |
} | |
extension TestArchivable: Arbitrary { | |
static var arbitrary: Gen<TestArchivable> { | |
return curry(TestArchivable.init) <^> String.arbitrary <*> Int.arbitrary <*> | |
NSTimeInterval.arbitrary <*> Optional<Int>.arbitrary | |
} | |
static func shrink(model: TestArchivable) -> [TestArchivable] { | |
return String.shrink(model.propertyA).flatMap { propertyA in | |
return Int.shrink(model.propertyB).flatMap { propertyB in | |
return NSTimeInterval.shrink(model.propertyC).flatMap { propertyC in | |
return Optional<Int>.shrink(model.propertyD).flatMap { propertyD in | |
return TestArchivable(propertyA: propertyA, | |
propertyB: propertyB, | |
propertyC: propertyC, | |
propertyD: propertyD) | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment