Skip to content

Instantly share code, notes, and snippets.

@msanders
Last active March 3, 2017 19:10
Show Gist options
  • Save msanders/84c994c33dade469ea7b7019fc5fe507 to your computer and use it in GitHub Desktop.
Save msanders/84c994c33dade469ea7b7019fc5fe507 to your computer and use it in GitHub Desktop.
ArchivableSpec.swift
//
// 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(["!", "#", "$", "%", "&",
"'", "*", "+", "-", "/",
"=", "?", "^", "_", "`",
"{", "|", "}", "~", "."])
}
//
// 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