From c27b5b30acc81cec29725530bdb3eb9d23cb815b Mon Sep 17 00:00:00 2001
From: Cody Garvin <emig647@gmail.com>
Date: Mon, 22 Jun 2020 21:51:33 -0700
Subject: [PATCH 1/2] LIBMOBILE-77: Removed Quick and Nimble, updated unit
 tests

---
 Analytics.xcodeproj/project.pbxproj          |   8 -
 AnalyticsTests/AnalyticsTests.swift          | 373 ++++++++---------
 AnalyticsTests/AnalyticsUtilTests.swift      | 197 +++++----
 AnalyticsTests/AutoScreenReportingTest.swift | 134 +++---
 AnalyticsTests/ContextTest.swift             | 116 +++---
 AnalyticsTests/CryptoTest.swift              |  88 ++--
 AnalyticsTests/FileStorageTest.swift         | 200 ++++-----
 AnalyticsTests/HTTPClientTest.swift          | 410 +++++++++----------
 AnalyticsTests/IntegrationsManagerTest.swift | 173 ++++----
 AnalyticsTests/MiddlewareTests.swift         | 233 +++++------
 AnalyticsTests/StoreKitTrackerTests.swift    |  46 +--
 AnalyticsTests/TrackingTests.swift           | 165 ++++----
 AnalyticsTests/UserDefaultsStorageTest.swift | 153 ++++---
 AnalyticsTests/Utils/TestUtils.swift         |  29 +-
 Podfile                                      |   2 -
 Podfile.lock                                 |  12 +-
 16 files changed, 1113 insertions(+), 1226 deletions(-)

diff --git a/Analytics.xcodeproj/project.pbxproj b/Analytics.xcodeproj/project.pbxproj
index e7631f89b..9f61cce48 100644
--- a/Analytics.xcodeproj/project.pbxproj
+++ b/Analytics.xcodeproj/project.pbxproj
@@ -589,16 +589,12 @@
 			);
 			inputPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-AnalyticsTests/Pods-AnalyticsTests-frameworks.sh",
-				"${BUILT_PRODUCTS_DIR}/Nimble-iOS/Nimble.framework",
 				"${BUILT_PRODUCTS_DIR}/Nocilla-iOS/Nocilla.framework",
-				"${BUILT_PRODUCTS_DIR}/Quick-iOS/Quick.framework",
 				"${BUILT_PRODUCTS_DIR}/SwiftTryCatch-iOS/SwiftTryCatch.framework",
 			);
 			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nocilla.framework",
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftTryCatch.framework",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -657,16 +653,12 @@
 			);
 			inputPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-AnalyticsTestsTVOS/Pods-AnalyticsTestsTVOS-frameworks.sh",
-				"${BUILT_PRODUCTS_DIR}/Nimble-tvOS/Nimble.framework",
 				"${BUILT_PRODUCTS_DIR}/Nocilla-tvOS/Nocilla.framework",
-				"${BUILT_PRODUCTS_DIR}/Quick-tvOS/Quick.framework",
 				"${BUILT_PRODUCTS_DIR}/SwiftTryCatch-tvOS/SwiftTryCatch.framework",
 			);
 			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nocilla.framework",
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftTryCatch.framework",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/AnalyticsTests/AnalyticsTests.swift b/AnalyticsTests/AnalyticsTests.swift
index 53b026b6b..baa657089 100644
--- a/AnalyticsTests/AnalyticsTests.swift
+++ b/AnalyticsTests/AnalyticsTests.swift
@@ -7,227 +7,212 @@
 //
 
 
-import Quick
-import Nimble
 import Analytics
+import XCTest
 
-class AnalyticsTests: QuickSpec {
-  override func spec() {
+class AnalyticsTests: XCTestCase {
+    
     let config = AnalyticsConfiguration(writeKey: "QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE")
     let cachedSettings = [
-      "integrations": [
-        "Segment.io": ["apiKey": "QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE"]
-      ],
-      "plan": ["track": [:]],
-    ] as NSDictionary
+        "integrations": [
+            "Segment.io": ["apiKey": "QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE"]
+        ],
+        "plan": ["track": [:]],
+        ] as NSDictionary
     var analytics: Analytics!
     var testMiddleware: TestMiddleware!
     var testApplication: TestApplication!
-
-    beforeEach {
-      testMiddleware = TestMiddleware()
-      config.sourceMiddleware = [testMiddleware]
-      testApplication = TestApplication()
-      config.application = testApplication
-      config.trackApplicationLifecycleEvents = true
-
-      UserDefaults.standard.set("test SEGQueue should be removed", forKey: "SEGQueue")
-      expect(UserDefaults.standard.string(forKey: "SEGQueue")).toNot(beNil())
-      
-      analytics = Analytics(configuration: config)
-      analytics.test_integrationsManager()?.test_setCachedSettings(settings: cachedSettings)
+    
+    override func setUp() {
+        super.setUp()
+        testMiddleware = TestMiddleware()
+        config.sourceMiddleware = [testMiddleware]
+        testApplication = TestApplication()
+        config.application = testApplication
+        config.trackApplicationLifecycleEvents = true
+        
+        UserDefaults.standard.set("test SEGQueue should be removed", forKey: "SEGQueue")
+        XCTAssertNotNil(UserDefaults.standard.string(forKey: "SEGQueue"))
+        
+        analytics = Analytics(configuration: config)
+        analytics.test_integrationsManager()?.test_setCachedSettings(settings: cachedSettings)
     }
-
-    afterEach {
-      analytics.reset()
+    
+    override func tearDown() {
+        super.tearDown()
+        analytics.reset()
     }
-
-    it("initialized correctly") {
-      expect(analytics.configuration.flushAt) == 20
-      expect(analytics.configuration.flushInterval) == 30
-      expect(analytics.configuration.maxQueueSize) == 1000
-      expect(analytics.configuration.writeKey) == "QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE"
-      expect(analytics.configuration.shouldUseLocationServices) == false
-      expect(analytics.configuration.enableAdvertisingTracking) == true
-      expect(analytics.configuration.shouldUseBluetooth) == false
-      expect(analytics.configuration.httpSessionDelegate).to(beNil())
-      expect(analytics.getAnonymousId()).toNot(beNil())
+    
+    func testInitializedCorrectly() {
+        XCTAssertEqual(analytics.configuration.flushAt, 20)
+        XCTAssertEqual(analytics.configuration.flushInterval, 30)
+        XCTAssertEqual(analytics.configuration.maxQueueSize, 1000)
+        XCTAssertEqual(analytics.configuration.writeKey, "QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE")
+        XCTAssertEqual(analytics.configuration.shouldUseLocationServices, false)
+        XCTAssertEqual(analytics.configuration.enableAdvertisingTracking, true)
+        XCTAssertEqual(analytics.configuration.shouldUseBluetooth,  false)
+        XCTAssertNil(analytics.configuration.httpSessionDelegate)
+        XCTAssertNotNil(analytics.getAnonymousId())
     }
     
-    it("clears SEGQueue from UserDefaults after initialized") {
-      expect(UserDefaults.standard.string(forKey: "SEGQueue")).toEventually(beNil())
+    func testClearsSEGQueueFromUserDefaults() {
+        expectUntil(2.0, expression: UserDefaults.standard.string(forKey: "SEGQueue") == nil)
     }
     
     /* TODO: Fix me when the Context object isn't so wild.
-    it("collects IDFA") {
-      testMiddleware.swallowEvent = true
-      analytics.configuration.enableAdvertisingTracking = true
-      analytics.configuration.adSupportBlock = { () -> String in
-        return "1234AdsNoMore!"
-      }
-
-      analytics.track("test");
-      
-      let event = testMiddleware.lastContext?.payload as? SEGTrackPayload
-      expect(event?.properties?["url"] as? String) == "myapp://auth?token=((redacted/my-auth))&other=stuff"
-    }*/
+     func testCollectsIDFA() {
+     testMiddleware.swallowEvent = true
+     analytics.configuration.enableAdvertisingTracking = true
+     analytics.configuration.adSupportBlock = { () -> String in
+     return "1234AdsNoMore!"
+     }
+     
+     analytics.track("test");
+     
+     let event = testMiddleware.lastContext?.payload as? TrackPayload
+     XCTAssertEqual(event?.properties?["url"] as? String, "myapp://auth?token=((redacted/my-auth))&other=stuff")
+     }*/
     
-    it("persists anonymousId") {
-      let analytics2 = Analytics(configuration: config)
-      expect(analytics.getAnonymousId()) == analytics2.getAnonymousId()
+    func testPersistsAnonymousId() {
+        let analytics2 = Analytics(configuration: config)
+        XCTAssertEqual(analytics.getAnonymousId(), analytics2.getAnonymousId())
     }
-
-    it("persists userId") {
-      analytics.identify("testUserId1")
-
-      let analytics2 = Analytics(configuration: config)
-      analytics2.test_integrationsManager()?.test_setCachedSettings(settings: cachedSettings)
-
-      expect(analytics.test_integrationsManager()?.test_segmentIntegration()?.test_userId()) == "testUserId1"
-      expect(analytics2.test_integrationsManager()?.test_segmentIntegration()?.test_userId()) == "testUserId1"
+    
+    func testPersistsUserId() {
+        analytics.identify("testUserId1")
+        
+        let analytics2 = Analytics(configuration: config)
+        analytics2.test_integrationsManager()?.test_setCachedSettings(settings: cachedSettings)
+        
+        XCTAssertEqual(analytics.test_integrationsManager()?.test_segmentIntegration()?.test_userId(), "testUserId1")
+        XCTAssertEqual(analytics2.test_integrationsManager()?.test_segmentIntegration()?.test_userId(), "testUserId1")
     }
-
-    /* This test is pretty flaky.
-    it("continues user activity") {
-      let activity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
-      activity.webpageURL = URL(string: "http://www.segment.com")
-      analytics.continue(activity)
-      let referrer = analytics.test_integrationsManager()?.test_segmentIntegration()?.test_referrer()
-      expect(referrer?["url"] as? String).toEventually(equal("http://www.segment.com"))
-    }
-    */
-
-    it("clears user data") {
-      analytics.identify("testUserId1", traits: [ "Test trait key" : "Test trait value"])
-      analytics.reset()
-      expect(analytics.test_integrationsManager()?.test_segmentIntegration()?.test_userId()).toEventually(beNil())
-      expect(analytics.test_integrationsManager()?.test_segmentIntegration()?.test_traits()?.count).toEventually(equal(0))
+    
+    func testClearsUserData() {
+        analytics.identify("testUserId1", traits: [ "Test trait key" : "Test trait value"])
+        analytics.reset()
+        
+        expectUntil(2.0, expression: self.analytics.test_integrationsManager()?.test_segmentIntegration()?.test_userId() == nil)
+        
+        expectUntil(2.0, expression: self.analytics.test_integrationsManager()?.test_segmentIntegration()?.test_traits()?.count == 0)
     }
-
-    it("fires Application Opened for UIApplicationDidFinishLaunching") {
-      testMiddleware.swallowEvent = true
-      NotificationCenter.default.post(name: .UIApplicationDidFinishLaunching, object: testApplication, userInfo: [
-        UIApplicationLaunchOptionsKey.sourceApplication: "testApp",
-        UIApplicationLaunchOptionsKey.url: "test://test",
-      ])
-      let event = testMiddleware.lastContext?.payload as? TrackPayload
-      expect(event?.event) == "Application Opened"
-      expect(event?.properties?["from_background"] as? Bool) == false
-      expect(event?.properties?["referring_application"] as? String) == "testApp"
-      expect(event?.properties?["url"] as? String) == "test://test"
+    
+    func testFiresApplicationOpenedForAppLaunchingEvent() {
+        testMiddleware.swallowEvent = true
+        NotificationCenter.default.post(name: .UIApplicationDidFinishLaunching, object: testApplication, userInfo: [
+            UIApplicationLaunchOptionsKey.sourceApplication: "testApp",
+            UIApplicationLaunchOptionsKey.url: "test://test",
+        ])
+        let event = testMiddleware.lastContext?.payload as? TrackPayload
+        XCTAssertEqual(event?.event, "Application Opened")
+        XCTAssertEqual(event?.properties?["from_background"] as? Bool, false)
+        XCTAssertEqual(event?.properties?["referring_application"] as? String, "testApp")
+        XCTAssertEqual(event?.properties?["url"] as? String, "test://test")
     }
-
-    it("fires Application Opened during UIApplicationWillEnterForeground") {
-      testMiddleware.swallowEvent = true
-      NotificationCenter.default.post(name: .UIApplicationWillEnterForeground, object: testApplication)
-      let event = testMiddleware.lastContext?.payload as? TrackPayload
-      expect(event?.event) == "Application Opened"
-      expect(event?.properties?["from_background"] as? Bool) == true
+    
+    func testFiresApplicationEnterForeground() {
+        testMiddleware.swallowEvent = true
+        NotificationCenter.default.post(name: .UIApplicationWillEnterForeground, object: testApplication)
+        let event = testMiddleware.lastContext?.payload as? TrackPayload
+        XCTAssertEqual(event?.event, "Application Opened")
+        XCTAssertEqual(event?.properties?["from_background"] as? Bool, true)
     }
     
-    it("fires Application Backgrounded during UIApplicationDidEnterBackground") {
-      testMiddleware.swallowEvent = true
-      NotificationCenter.default.post(name: .UIApplicationDidEnterBackground, object: testApplication)
-      let event = testMiddleware.lastContext?.payload as? TrackPayload
-      expect(event?.event) == "Application Backgrounded"
+    func testFiresApplicationDuringEnterBackground() {
+        testMiddleware.swallowEvent = true
+        NotificationCenter.default.post(name: .UIApplicationDidEnterBackground, object: testApplication)
+        let event = testMiddleware.lastContext?.payload as? TrackPayload
+        XCTAssertEqual(event?.event, "Application Backgrounded")
     }
-
-    it("flushes when UIApplicationDidEnterBackgroundNotification is fired") {
-      analytics.track("test")
-      NotificationCenter.default.post(name: .UIApplicationDidEnterBackground, object: testApplication)
-      expect(testApplication.backgroundTasks.count).toEventually(equal(1))
-      expect(testApplication.backgroundTasks[0].isEnded).toEventually(beFalse())
+    
+    func testFlushesWhenApplicationBackgroundIsFired() {
+        analytics.track("test")
+        NotificationCenter.default.post(name: .UIApplicationDidEnterBackground, object: testApplication)
+        
+        expectUntil(2.0, expression: self.testApplication.backgroundTasks.count == 1)
+        expectUntil(2.0, expression: self.testApplication.backgroundTasks[0].isEnded == false)
     }
     
-    it("respects maxQueueSize") {
-      let max = 72
-      config.maxQueueSize = UInt(max)
-
-      for i in 1...max * 2 {
-        analytics.track("test #\(i)")
-      }
-
-      let integration = analytics.test_integrationsManager()?.test_segmentIntegration()
-      expect(integration).notTo(beNil())
-      
-      analytics.flush()
-      waitUntil(timeout: 60) {done in
-        let queue = DispatchQueue(label: "test")
-        
-        queue.async {
-          while(integration?.test_queue()?.count != max) {
+    func testRespectsMaxQueueSize() {
+        let max = 72
+        config.maxQueueSize = UInt(max)
+        
+        for i in 1...max * 2 {
+            analytics.track("test #\(i)")
+        }
+        
+        let integration = analytics.test_integrationsManager()?.test_segmentIntegration()
+        XCTAssertNotNil(integration)
+        
+        analytics.flush()
+        let currentTime = Date()
+        while(integration?.test_queue()?.count != max && currentTime < currentTime + 60) {
             sleep(1)
-          }
-
-          done()
         }
-      }
     }
-
-    it("protocol conformance should not interfere with UIApplication interface") {
-      // In Xcode8/iOS10, UIApplication.h typedefs UIBackgroundTaskIdentifier as NSUInteger,
-      // whereas Swift has UIBackgroundTaskIdentifier typealiaed to Int.
-      // This is likely due to a custom Swift mapping for UIApplication which got out of sync.
-      // If we extract the exact UIApplication method names in SEGApplicationProtocol,
-      // it will cause a type mismatch between the return value from beginBackgroundTask
-      // and the argument for endBackgroundTask.
-      // This would impact all code in a project that imports the Segment framework.
-      // Note that this doesn't appear to be an issue any longer in Xcode9b3.
-      let task = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
-      UIApplication.shared.endBackgroundTask(task)
-    }
-    
-    it("flushes using flushTimer") {
-      let integration = analytics.test_integrationsManager()?.test_segmentIntegration()
-
-      analytics.track("test")
-
-      expect(integration?.test_flushTimer()).toEventuallyNot(beNil())
-      expect(integration?.test_batchRequest()).to(beNil())
-
-      integration?.test_flushTimer()?.fire()
-      
-      expect(integration?.test_batchRequest()).toEventuallyNot(beNil())
+    
+    func testProtocolConformanceShouldNotInterfere() {
+        // In Xcode8/iOS10, UIApplication.h typedefs UIBackgroundTaskIdentifier as NSUInteger,
+        // whereas Swift has UIBackgroundTaskIdentifier typealiaed to Int.
+        // This is likely due to a custom Swift mapping for UIApplication which got out of sync.
+        // If we extract the exact UIApplication method names in SEGApplicationProtocol,
+        // it will cause a type mismatch between the return value from beginBackgroundTask
+        // and the argument for endBackgroundTask.
+        // This would impact all code in a project that imports the Segment framework.
+        // Note that this doesn't appear to be an issue any longer in Xcode9b3.
+        let task = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
+        UIApplication.shared.endBackgroundTask(task)
     }
-
-    it("respects flushInterval") {
-      let timer = analytics
-        .test_integrationsManager()?
-        .test_segmentIntegration()?
-        .test_flushTimer()
-      
-      expect(timer).toNot(beNil())
-      expect(timer?.timeInterval) == config.flushInterval
-    }
-    
-    it("redacts sensible URLs from deep links tracking") {
-      testMiddleware.swallowEvent = true
-      analytics.configuration.trackDeepLinks = true
-      analytics.open(URL(string: "fb123456789://authorize#access_token=hastoberedacted")!, options: [:])
-      
-      
-      let event = testMiddleware.lastContext?.payload as? TrackPayload
-      expect(event?.event) == "Deep Link Opened"
-      expect(event?.properties?["url"] as? String) == "fb123456789://authorize#access_token=((redacted/fb-auth-token))"
+    
+    func testFlushesUsingFlushTimer() {
+        let integration = analytics.test_integrationsManager()?.test_segmentIntegration()
+        
+        analytics.track("test")
+        
+        expectUntil(2.0, expression: integration?.test_flushTimer() != nil)
+        XCTAssertNil(integration?.test_batchRequest())
+        
+        integration?.test_flushTimer()?.fire()
+        expectUntil(2.0, expression: integration?.test_batchRequest() != nil)
+    }
+    
+    func testRespectsFlushIntervale() {
+        let timer = analytics
+            .test_integrationsManager()?
+            .test_segmentIntegration()?
+            .test_flushTimer()
+        
+        XCTAssertNotNil(timer)
+        XCTAssertEqual(timer?.timeInterval, config.flushInterval)
+    }
+    
+    func testRedactsSensibleURLsFromDeepLinksTracking() {
+        testMiddleware.swallowEvent = true
+        analytics.configuration.trackDeepLinks = true
+        analytics.open(URL(string: "fb123456789://authorize#access_token=hastoberedacted")!, options: [:])
+        
+        
+        let event = testMiddleware.lastContext?.payload as? TrackPayload
+        XCTAssertEqual(event?.event, "Deep Link Opened")
+        XCTAssertEqual(event?.properties?["url"] as? String, "fb123456789://authorize#access_token=((redacted/fb-auth-token))")
+    }
+    
+    func testRedactsSensibleURLsFromDeepLinksWithFilters() {
+        testMiddleware.swallowEvent = true
+        analytics.configuration.payloadFilters["(myapp://auth\\?token=)([^&]+)"] = "$1((redacted/my-auth))"
+        analytics.configuration.trackDeepLinks = true
+        analytics.open(URL(string: "myapp://auth?token=hastoberedacted&other=stuff")!, options: [:])
+        
+        
+        let event = testMiddleware.lastContext?.payload as? TrackPayload
+        XCTAssertEqual(event?.event, "Deep Link Opened")
+        XCTAssertEqual(event?.properties?["url"] as? String, "myapp://auth?token=((redacted/my-auth))&other=stuff")
+    }
+    
+    func testDefaultsSEGQueueToEmptyArray() {
+        let integration = analytics.test_integrationsManager()?.test_segmentIntegration()
+        XCTAssertNotNil(integration)
+        integration?.test_fileStorage()?.resetAll()
+        XCTAssert(integration?.test_queue()?.isEmpty ?? false)
     }
-
-    it("redacts sensible URLs from deep links tracking using custom filters") {
-      testMiddleware.swallowEvent = true
-      analytics.configuration.payloadFilters["(myapp://auth\\?token=)([^&]+)"] = "$1((redacted/my-auth))"
-      analytics.configuration.trackDeepLinks = true
-      analytics.open(URL(string: "myapp://auth?token=hastoberedacted&other=stuff")!, options: [:])
-      
-      
-      let event = testMiddleware.lastContext?.payload as? TrackPayload
-      expect(event?.event) == "Deep Link Opened"
-      expect(event?.properties?["url"] as? String) == "myapp://auth?token=((redacted/my-auth))&other=stuff"
-    }
-    
-    it("defaults SEGQueue to an empty array when missing from file storage") {
-      let integration = analytics.test_integrationsManager()?.test_segmentIntegration()
-      expect(integration).notTo(beNil())
-      integration?.test_fileStorage()?.resetAll()
-      expect(integration?.test_queue()).to(beEmpty())
-    }
-  }
 }
diff --git a/AnalyticsTests/AnalyticsUtilTests.swift b/AnalyticsTests/AnalyticsUtilTests.swift
index 53998922f..1bc075d45 100644
--- a/AnalyticsTests/AnalyticsUtilTests.swift
+++ b/AnalyticsTests/AnalyticsUtilTests.swift
@@ -7,134 +7,127 @@
 //
 
 
-import Quick
-import Nimble
 import Analytics
+import XCTest
 
-class AnalyticsUtilTests: QuickSpec {
-  override func spec() {
-
-    it("format NSDate objects to RFC 3339 complaint string") {
-      let date = Date(timeIntervalSince1970: 0)
-      let formattedString = iso8601FormattedString(date)
-      expect(formattedString) == "1970-01-01T00:00:00.000Z"
-
-      var components = DateComponents()
-      components.year = 1992
-      components.month = 8
-      components.day = 6
-      components.hour = 7
-      components.minute = 32
-      components.second = 4
-      components.nanosecond = 335000000
-      let calendar = NSCalendar(calendarIdentifier: .gregorian)!
-      calendar.timeZone = TimeZone(secondsFromGMT: -4 * 60 * 60)!
-      let date2 = calendar.date(from: components)!
-      let formattedString2 = iso8601FormattedString(date2)
-      expect(formattedString2) == "1992-08-06T11:32:04.335Z"
+class AnalyticsUtilTests: XCTestCase {
+    
+    let filters = [
+        "(foo)": "$1-bar"
+    ]
+    
+    func equals(a: Any, b: Any) -> Bool {
+        let aData = try! JSONSerialization.data(withJSONObject: a, options: .prettyPrinted) as NSData
+        let bData = try! JSONSerialization.data(withJSONObject: b, options: .prettyPrinted)
+        
+        return aData.isEqual(to: bData)
     }
-
-    it("format NSDate objects to RFC 3339 complaint string w/ nanoseconds") {
-      let date = Date(timeIntervalSince1970: 0)
-      let formattedString = iso8601NanoFormattedString(date)
-      expect(formattedString) == "1970-01-01T00:00:00.000000000Z"
-
-      var components = DateComponents()
-      components.year = 1992
-      components.month = 8
-      components.day = 6
-      components.hour = 7
-      components.minute = 32
-      components.second = 4
-      components.nanosecond = 335000008
-      let calendar = NSCalendar(calendarIdentifier: .gregorian)!
-      calendar.timeZone = TimeZone(secondsFromGMT: -4 * 60 * 60)!
-      let date2 = calendar.date(from: components)!
-      let formattedString2 = iso8601NanoFormattedString(date2)
-      expect(formattedString2) == "1992-08-06T11:32:04.335000008Z"
+    
+    func testFormatNSDateObjects() {
+        let date = Date(timeIntervalSince1970: 0)
+        let formattedString = iso8601FormattedString(date)
+        XCTAssertEqual(formattedString, "1970-01-01T00:00:00.000Z")
+        
+        var components = DateComponents()
+        components.year = 1992
+        components.month = 8
+        components.day = 6
+        components.hour = 7
+        components.minute = 32
+        components.second = 4
+        components.nanosecond = 335000000
+        let calendar = NSCalendar(calendarIdentifier: .gregorian)!
+        calendar.timeZone = TimeZone(secondsFromGMT: -4 * 60 * 60)!
+        let date2 = calendar.date(from: components)!
+        let formattedString2 = iso8601FormattedString(date2)
+        XCTAssertEqual(formattedString2, "1992-08-06T11:32:04.335Z")
     }
-
-    describe("trimQueue", {
-      it("does nothing when count < max") {
+    
+    func testFormatNSDateRFC3339() {
+        let date = Date(timeIntervalSince1970: 0)
+        let formattedString = iso8601NanoFormattedString(date)
+        XCTAssertEqual(formattedString, "1970-01-01T00:00:00.000000000Z")
+        
+        var components = DateComponents()
+        components.year = 1992
+        components.month = 8
+        components.day = 6
+        components.hour = 7
+        components.minute = 32
+        components.second = 4
+        components.nanosecond = 335000008
+        let calendar = NSCalendar(calendarIdentifier: .gregorian)!
+        calendar.timeZone = TimeZone(secondsFromGMT: -4 * 60 * 60)!
+        let date2 = calendar.date(from: components)!
+        let formattedString2 = iso8601NanoFormattedString(date2)
+        XCTAssertEqual(formattedString2, "1992-08-06T11:32:04.335000008Z")
+    }
+    
+    func testTrimQueueDoesNothingCountLessThan() {
         let queue = NSMutableArray(array: [])
         for i in 1...4 {
-          queue.add(i)
+            queue.add(i)
         }
         trimQueue(queue, 5)
-        expect(queue) == [1, 2, 3, 4]
-      }
-
-      it("trims when count > max") {
+        XCTAssertEqual(queue, [1, 2, 3, 4])
+    }
+    
+    func testTrimQueueWhenCountGreaterThan() {
         let queue = NSMutableArray(array: [])
         for i in 1...10 {
-          queue.add(i)
+            queue.add(i)
         }
         trimQueue(queue, 5)
-        expect(queue) == [6, 7, 8, 9, 10]
-      }
-
-      it("does not trim when count == max") {
+        XCTAssertEqual(queue, [6, 7, 8, 9, 10])
+    }
+    
+    func testTrimQueueWhenCountEqual() {
         let queue = NSMutableArray(array: [])
         for i in 1...5 {
-          queue.add(i)
+            queue.add(i)
         }
         trimQueue(queue, 5)
-        expect(queue) == [1, 2, 3, 4, 5]
-      }
-    })
+        XCTAssertEqual(queue, [1, 2, 3, 4, 5])
+    }
     
-    describe("JSON traverse", {
-      let filters = [
-        "(foo)": "$1-bar"
-      ]
-      
-      func equals(a: Any, b: Any) -> Bool {
-        let aData = try! JSONSerialization.data(withJSONObject: a, options: .prettyPrinted) as NSData
-        let bData = try! JSONSerialization.data(withJSONObject: b, options: .prettyPrinted)
-        
-        return aData.isEqual(to: bData)
-      }
-
-      it("works with strings") {
-        expect(Utilities.traverseJSON("a b foo c", andReplaceWithFilters: filters) as? String) == "a b foo-bar c"
-      }
-
-      it("works recursively") {
-        expect(Utilities.traverseJSON("a b foo foo c", andReplaceWithFilters: filters) as? String) == "a b foo-bar foo-bar c"
-      }
-      
-      it("works with nested dictionaries") {
+    func testJSONTraverseWithStrings() {
+        XCTAssertEqual(Utilities.traverseJSON("a b foo c", andReplaceWithFilters: filters) as? String, "a b foo-bar c")
+    }
+    
+    func testJSONTraverseRecursively() {
+        XCTAssertEqual(Utilities.traverseJSON("a b foo foo c", andReplaceWithFilters: filters) as? String, "a b foo-bar foo-bar c")
+    }
+    
+    func testJSONWorksNestedDictionaries() {
         let data = [
-          "foo": [1, nil, "qfoob", ["baz": "foo"]],
-          "bar": "foo"
-        ] as [String: Any]
+            "foo": [1, nil, "qfoob", ["baz": "foo"]],
+            "bar": "foo"
+            ] as [String: Any]
         
         guard let input = Utilities.traverseJSON(data, andReplaceWithFilters: filters) as? [String: Any] else {
-          XCTFail("Failed to create actual result from traversed JSON replace")
-          return
+            XCTFail("Failed to create actual result from traversed JSON replace")
+            return
         }
         
         let output = [
-          "foo": [1, nil, "qfoo-barb", ["baz": "foo-bar"]],
-          "bar": "foo-bar"
-        ] as [String: Any]
+            "foo": [1, nil, "qfoo-barb", ["baz": "foo-bar"]],
+            "bar": "foo-bar"
+            ] as [String: Any]
         
-        expect(NSDictionary(dictionary: output).isEqual(to: input)) == true
-      }
-      
-      it("works with nested arrays") {
+        XCTAssertEqual(NSDictionary(dictionary: output).isEqual(to: input), true)
+    }
+    
+    func testJSONWorksNestedArrays() {
         let data = [
-          [1, nil, "qfoob", ["baz": "foo"]],
-          "foo"
-          ] as [Any]
+            [1, nil, "qfoob", ["baz": "foo"]],
+            "foo"
+            ] as [Any]
         let input = Utilities.traverseJSON(data, andReplaceWithFilters: filters)
         let output = [
-          [1, nil, "qfoo-barb", ["baz": "foo-bar"]],
-          "foo-bar"
-          ] as [Any]
+            [1, nil, "qfoo-barb", ["baz": "foo-bar"]],
+            "foo-bar"
+            ] as [Any]
         
-        expect(equals(a: input!, b: output)) == true
-      }
-    })
-  }
+        XCTAssertEqual(equals(a: input!, b: output), true)
+    }
 }
diff --git a/AnalyticsTests/AutoScreenReportingTest.swift b/AnalyticsTests/AutoScreenReportingTest.swift
index a574b9024..b58a7821d 100644
--- a/AnalyticsTests/AutoScreenReportingTest.swift
+++ b/AnalyticsTests/AutoScreenReportingTest.swift
@@ -4,133 +4,91 @@
 //
 
 import Foundation
-import Quick
-import Nimble
 import SwiftTryCatch
 @testable import Analytics
+import XCTest
 
-class AutoScreenReportingTests: QuickSpec {
-
-  override func spec() {
-
+class AutoScreenReportingTests: XCTestCase {
+    
     var window: UIWindow!
     var rootVC: UIViewController!
-
-    beforeEach {
-      let config = AnalyticsConfiguration(writeKey: "foobar")
-      config.trackApplicationLifecycleEvents = true
-      config.recordScreenViews = true
-
-      window = UIWindow()
-      rootVC = UIViewController()
-      window.addSubview(rootVC.view)
+    
+    override func setUp() {
+        super.setUp()
+        
+        let config = AnalyticsConfiguration(writeKey: "foobar")
+        config.trackApplicationLifecycleEvents = true
+        config.recordScreenViews = true
+        
+        window = UIWindow()
+        rootVC = UIViewController()
+        window.addSubview(rootVC.view)
     }
-
-
-    describe("given a single view controller") {
-
-      it("seg_topViewController returns that view controller") {
+    
+    func testTopViewControllerReturnController() {
         let actualVC = UIViewController.seg_topViewController(rootVC)
-        expect(actualVC) === rootVC
-      }
+        XCTAssertEqual(actualVC, rootVC)
     }
-
-    describe("given a presented view controller") {
-
-      var expectedVC: UIViewController!
-
-      beforeEach {
+    
+    func testTopViewControllerReturnsPresentedController() {
+        var expectedVC: UIViewController!
         expectedVC = UIViewController()
         rootVC.present(expectedVC, animated: false)
-      }
-
-      it("seg_topViewController returns the presented view controller") {
         let actualVC = UIViewController.seg_topViewController(rootVC)
-        expect(actualVC) === expectedVC
-      }
+        XCTAssertEqual(actualVC, expectedVC)
+        
     }
-
-    describe("given a pushed view controller") {
-
-      var expectedVC: UIViewController!
-
-      beforeEach {
+    
+    func testTopViewControllerReturnsPushedViewController() {
+        var expectedVC: UIViewController!
         expectedVC = UIViewController()
         let nc = UINavigationController()
         rootVC.present(nc, animated: false)
         nc.pushViewController(expectedVC, animated: false)
-      }
-
-      it("seg_topViewController returns the pushed view controller") {
         let actualVC = UIViewController.seg_topViewController(rootVC)
-        expect(actualVC) === expectedVC
-      }
+        XCTAssertEqual(actualVC, expectedVC)
     }
-
-    describe("given a child of a UITabBarController") {
-
-      var expectedVC: UIViewController!
-
-      beforeEach {
+    
+    func testTopViewControllerTeturnsCurrentSelectedController() {
+        var expectedVC: UIViewController!
         expectedVC = UIViewController()
         let tabBarController = UITabBarController()
         rootVC.present(tabBarController, animated: false)
         tabBarController.viewControllers = [UIViewController(), expectedVC, UIViewController()]
         tabBarController.selectedIndex = 1
-      }
-
-      it("seg_topViewController returns the currently selected view controller") {
         let actualVC = UIViewController.seg_topViewController(rootVC)
-        expect(actualVC) === expectedVC
-      }
+        XCTAssertEqual(actualVC, expectedVC)
     }
-
-    describe("given a child of a custom container view controller conforming to SEGScreenReporting") {
-
-      class CustomContainerViewController: UIViewController, SEGScreenReporting {
-        var selectedIndex: Int = 0
-        var seg_mainViewController: UIViewController? {
-          return childViewControllers[selectedIndex]
+    
+    func testTopViewControllerReturnsCurrentSelectedViewController() {
+        class CustomContainerViewController: UIViewController, SEGScreenReporting {
+            var selectedIndex: Int = 0
+            var seg_mainViewController: UIViewController? {
+                return childViewControllers[selectedIndex]
+            }
         }
-      }
-
-      var expectedVC: UIViewController!
-
-      beforeEach {
+        
+        var expectedVC: UIViewController!
         expectedVC = UIViewController()
         let containerVC = CustomContainerViewController()
         rootVC.present(containerVC, animated: false)
         [UIViewController(), expectedVC, UIViewController()].forEach { child in
-          containerVC.addChildViewController(child)
+            containerVC.addChildViewController(child)
         }
         containerVC.selectedIndex = 1
-      }
-
-      it("seg_topViewController returns the currently selected view controller") {
         let actualVC = UIViewController.seg_topViewController(rootVC)
-        expect(actualVC) === expectedVC
-      }
+        XCTAssertEqual(actualVC, expectedVC)
     }
-
-    describe("given a child of a container view controller NOT conforming to SEGScreenReporting") {
-
-      var expectedVC: UIViewController!
-
-      beforeEach {
+    
+    func testTopViewControllerReturnsChildViewController() {
+        var expectedVC: UIViewController!
         expectedVC = UIViewController()
         let containerVC = UIViewController()
         rootVC.present(containerVC, animated: false)
         [expectedVC, UIViewController(), UIViewController()].forEach { child in
-          containerVC.addChildViewController(child)
+            containerVC.addChildViewController(child)
         }
-      }
-
-      it("seg_topViewController returns the first child view controller") {
         let actualVC = UIViewController.seg_topViewController(rootVC)
-        expect(actualVC) === expectedVC
-      }
+        XCTAssertEqual(actualVC, expectedVC)
     }
-  }
 }
-
-
diff --git a/AnalyticsTests/ContextTest.swift b/AnalyticsTests/ContextTest.swift
index 972aa884f..950e2326a 100644
--- a/AnalyticsTests/ContextTest.swift
+++ b/AnalyticsTests/ContextTest.swift
@@ -6,82 +6,76 @@
 //  Copyright © 2016 Segment. All rights reserved.
 //
 
-import Quick
-import Nimble
 import SwiftTryCatch
 import Analytics
+import XCTest
 
-class ContextTests: QuickSpec {
-  override func spec() {
+class ContextTests: XCTestCase {
     
     var analytics: Analytics!
     
-    beforeEach {
-      let config = AnalyticsConfiguration(writeKey: "foobar")
-      analytics = Analytics(configuration: config)
+    override func setUp() {
+        super.setUp()
+        let config = AnalyticsConfiguration(writeKey: "foobar")
+        analytics = Analytics(configuration: config)
     }
     
-    it("throws when used incorrectly") {
-      var context: Context?
-      var exception: NSException?
-      
-      SwiftTryCatch.tryRun({
-        context = Context()
-      }, catchRun: { e in
-        exception = e
-      }, finallyRun: nil)
-      
-      expect(context).to(beNil())
-      expect(exception).toNot(beNil())
+    func testThrowsWhenUsedIncorrectly() {
+        var context: Context?
+        var exception: NSException?
+        
+        SwiftTryCatch.tryRun({
+            context = Context()
+        }, catchRun: { e in
+            exception = e
+        }, finallyRun: nil)
+        
+        XCTAssertNil(context)
+        XCTAssertNotNil(exception)
     }
-
     
-    it("initialized correctly") {
-      let context = Context(analytics: analytics)
-      expect(context._analytics) == analytics
-      expect(context.eventType) == EventType.undefined
+    func testInitializedCorrectly() {
+        let context = Context(analytics: analytics)
+        XCTAssertEqual(context._analytics, analytics)
+        XCTAssertEqual(context.eventType, EventType.undefined)
     }
     
-    it("accepts modifications") {
-      let context = Context(analytics: analytics)
-      
-      let newContext = context.modify { context in
-        context.userId = "sloth"
-        context.eventType = .track;
-      }
-      expect(newContext.userId) == "sloth"
-      expect(newContext.eventType) == EventType.track;
-      
+    func testAcceptsModifications() {
+        let context = Context(analytics: analytics)
+        
+        let newContext = context.modify { context in
+            context.userId = "sloth"
+            context.eventType = .track;
+        }
+        XCTAssertEqual(newContext.userId, "sloth")
+        XCTAssertEqual(newContext.eventType,  EventType.track)
     }
     
-    it("modifies copy in debug mode to catch bugs") {
-      let context = Context(analytics: analytics).modify { context in
-        context.debug = true
-      }
-      expect(context.debug) == true
-      
-      let newContext = context.modify { context in
-        context.userId = "123"
-      }
-      expect(context) !== newContext
-      expect(newContext.userId) == "123"
-      expect(context.userId).to(beNil())
+    func testModifiesCopyInDebugMode() {
+        let context = Context(analytics: analytics).modify { context in
+            context.debug = true
+        }
+        XCTAssertEqual(context.debug, true)
+        
+        let newContext = context.modify { context in
+            context.userId = "123"
+        }
+        XCTAssertNotEqual(context, newContext)
+        XCTAssertEqual(newContext.userId, "123")
+        XCTAssertNil(context.userId)
     }
     
-    it("modifies self in non-debug mode to optimize perf.") {
-      let context = Context(analytics: analytics).modify { context in
-        context.debug = false
-      }
-      expect(context.debug) == false
-      
-      let newContext = context.modify { context in
-        context.userId = "123"
-      }
-      expect(context) === newContext
-      expect(newContext.userId) == "123"
-      expect(context.userId) == "123"
+    func testModifiesSelfInNonDebug() {
+        let context = Context(analytics: analytics).modify { context in
+            context.debug = false
+        }
+        XCTAssertFalse(context.debug)
+        
+        let newContext = context.modify { context in
+            context.userId = "123"
+        }
+        XCTAssertEqual(context, newContext)
+        XCTAssertEqual(newContext.userId, "123")
+        XCTAssertEqual(context.userId, "123")
     }
-    
-  }
-  
 }
diff --git a/AnalyticsTests/CryptoTest.swift b/AnalyticsTests/CryptoTest.swift
index daa179283..053e4f05e 100644
--- a/AnalyticsTests/CryptoTest.swift
+++ b/AnalyticsTests/CryptoTest.swift
@@ -5,57 +5,57 @@
 //  Copyright © 2016 Segment. All rights reserved.
 //
 
-import Quick
-import Nimble
 import Analytics
+import XCTest
 
-class CryptoTest : QuickSpec {
-  override func spec() {
-    var crypto : AES256Crypto!
-    beforeEach {
-      crypto = AES256Crypto(password: "slothysloth")
+class CryptoTest : XCTestCase {
+    
+    var crypto: AES256Crypto!
+    override func setUp() {
+        super.setUp()
+        crypto = AES256Crypto(password: "slothysloth")
     }
     
-    it("encrypts and decrypts data") {
-      let strIn = "segment"
-      let dataIn = strIn.data(using: String.Encoding.utf8)!
-      let encryptedData = crypto.encrypt(dataIn)
-      expect(encryptedData).toNot(beNil())
-      
-      let dataOut = crypto.decrypt(encryptedData!)
-      expect(dataOut) == dataIn
-      
-      let strOut = String(data: dataOut!, encoding: String.Encoding.utf8)
-      expect(strOut) == "segment"
+    func testEncryptDecryptSuccess() {
+        let strIn = "segment"
+        let dataIn = strIn.data(using: String.Encoding.utf8)!
+        let encryptedData = crypto.encrypt(dataIn)
+        XCTAssert(encryptedData != nil, "Encrypted data should not be nil")
+        
+        let dataOut = crypto.decrypt(encryptedData!)
+        XCTAssert(dataOut == dataIn, "Data should be the same")
+        
+        let strOut = String(data: dataOut!, encoding: String.Encoding.utf8)
+        XCTAssertEqual(strOut, "segment", "Strings should be the same")
     }
     
-    it("fails for incorrect password") {
-      let strIn = "segment"
-      let dataIn = strIn.data(using: String.Encoding.utf8)!
-      let encryptedData = crypto.encrypt(dataIn)
-      expect(encryptedData).toNot(beNil())
-      
-      let crypto2 = AES256Crypto(password: "wolf", salt: crypto.salt, iv: crypto.iv)
-      let dataOut = crypto2.decrypt(encryptedData!)
-      expect(dataOut) != dataIn
-      let strOut = String(data: dataOut!, encoding: String.Encoding.utf8)
-      // no built in way to check password correctness
-      // http://stackoverflow.com/questions/27712173/determine-if-key-is-incorrect-with-cccrypt-kccoptionpkcs7padding-objective-c
-      expect(strOut ?? "") != strIn
+    func testIncorrectPassword() {
+        let strIn = "segment"
+        let dataIn = strIn.data(using: String.Encoding.utf8)!
+        let encryptedData = crypto.encrypt(dataIn)
+        XCTAssert(encryptedData != nil, "Encrypted data should not be nil")
+        
+        let crypto2 = AES256Crypto(password: "wolf", salt: crypto.salt, iv: crypto.iv)
+        let dataOut = crypto2.decrypt(encryptedData!)
+        XCTAssertNotEqual(dataOut, dataIn, "In and out should not match")
+        
+        let strOut = String(data: dataOut!, encoding: String.Encoding.utf8)
+        // no built in way to check password correctness
+        // http://stackoverflow.com/questions/27712173/determine-if-key-is-incorrect-with-cccrypt-kccoptionpkcs7padding-objective-c
+        XCTAssertNotEqual(strOut ?? "", strIn, "String in and out should not match")
     }
     
-    it("fails for incorrect iv and sault") {
-      let strIn = "segment"
-      let dataIn = strIn.data(using: String.Encoding.utf8)!
-      let encryptedData = crypto.encrypt(dataIn)
-      expect(encryptedData).toNot(beNil())
-      
-      let crypto2 = AES256Crypto(password: crypto.password)
-      let dataOut = crypto2.decrypt(encryptedData!)
-      expect(dataOut) != dataIn
-      
-      let strOut = String(data: dataOut!, encoding: String.Encoding.utf8)
-      expect(strOut ?? "") != strIn
+    func testFailureForIVAndSault() {
+        let strIn = "segment"
+        let dataIn = strIn.data(using: String.Encoding.utf8)!
+        let encryptedData = crypto.encrypt(dataIn)
+        XCTAssertNotNil(encryptedData, "Encrypted data should not be nil")
+        
+        let crypto2 = AES256Crypto(password: crypto.password)
+        let dataOut = crypto2.decrypt(encryptedData!)
+        XCTAssertNotEqual(dataOut, dataIn, "Out and In data should not match")
+        
+        let strOut = String(data: dataOut!, encoding: String.Encoding.utf8)
+        XCTAssertNotEqual(strOut ?? "", strIn, "Out and In strings should not match")
     }
-  }
 }
diff --git a/AnalyticsTests/FileStorageTest.swift b/AnalyticsTests/FileStorageTest.swift
index 993318ccc..563cbc38a 100644
--- a/AnalyticsTests/FileStorageTest.swift
+++ b/AnalyticsTests/FileStorageTest.swift
@@ -5,127 +5,129 @@
 //  Copyright © 2016 Segment. All rights reserved.
 //
 
-import Quick
-import Nimble
 import Analytics
+import XCTest
 
-class FileStorageTest : QuickSpec {
-  override func spec() {
+
+class FileStorageTest : XCTestCase {
+    
     var storage : FileStorage!
-    beforeEach {
-      let url = FileStorage.applicationSupportDirectoryURL()
-      expect(url).toNot(beNil())
-      expect(url?.lastPathComponent) == "Application Support"
-      storage = FileStorage(folder: url!, crypto: nil)
+    
+    override func setUp() {
+        super.setUp()
+        let url = FileStorage.applicationSupportDirectoryURL()
+        XCTAssertNotNil(url, "URL Should not be nil")
+        XCTAssertEqual(url?.lastPathComponent, "Application Support")
+        storage = FileStorage(folder: url!, crypto: nil)
     }
     
-    it("Creates caches directory") {
-      let url = FileStorage.cachesDirectoryURL()
-      expect(url).toNot(beNil())
-      expect(url?.lastPathComponent) == "Caches"
+    override func tearDown() {
+        super.tearDown()
+        storage.resetAll()
     }
     
-    it("creates folder if none exists") {
-      let tempDir = NSURL(fileURLWithPath: NSTemporaryDirectory())
-      let url = tempDir.appendingPathComponent(NSUUID().uuidString)
-      
-      expect(try? url?.checkResourceIsReachable()).to(beNil())
-      _ = FileStorage(folder: url!, crypto: nil)
-      
-      var isDir: ObjCBool = false
-      let exists = FileManager.default.fileExists(atPath: url!.path, isDirectory: &isDir)
-      
-      expect(exists) == true
-      expect(isDir.boolValue) == true
+    func testCreatesCachesDirectory() {
+        let url = FileStorage.cachesDirectoryURL()
+        XCTAssertNotNil(url, "URL should not be nil")
+        XCTAssertEqual(url?.lastPathComponent, "Caches", "Last part of url should be Caches")
     }
     
-    it("persists and loads data") {
-      let dataIn = "segment".data(using: String.Encoding.utf8)!
-      storage.setData(dataIn, forKey: "mydata")
-      
-      let dataOut = storage.data(forKey: "mydata")
-      expect(dataOut) == dataIn
-      
-      let strOut = String(data: dataOut!, encoding: String.Encoding.utf8)
-      expect(strOut) == "segment"
+    func testCreatesFolderIfNoneExists() {
+        let tempDir = NSURL(fileURLWithPath: NSTemporaryDirectory())
+        let url = tempDir.appendingPathComponent(NSUUID().uuidString)
+        
+        XCTAssertNil(try? url?.checkResourceIsReachable() ?? true)
+        _ = FileStorage(folder: url!, crypto: nil)
+        
+        var isDir: ObjCBool = false
+        let exists = FileManager.default.fileExists(atPath: url!.path, isDirectory: &isDir)
+        
+        XCTAssertEqual(exists, true, "Exists should be true")
+        XCTAssertEqual(isDir.boolValue, true, "Should be a directory")
     }
     
-    it("persists and loads string") {
-      let str = "san francisco"
-      storage.setString(str, forKey: "city")
-      expect(storage.string(forKey: "city")) == str
-      
-      storage.removeKey("city")
-      expect(storage.string(forKey: "city")).to(beNil())
+    func testPersistsAndLoadsData() {
+        let dataIn = "segment".data(using: String.Encoding.utf8)!
+        storage.setData(dataIn, forKey: "mydata")
+        
+        let dataOut = storage.data(forKey: "mydata")
+        XCTAssertEqual(dataOut, dataIn, "Out and In data should match")
+        
+        let strOut = String(data: dataOut!, encoding: String.Encoding.utf8)
+        XCTAssertEqual(strOut, "segment")
     }
     
-    it("persists and loads array") {
-      let array = [
-        "san francisco",
-        "new york",
-        "tallinn",
-      ]
-      storage.setArray(array, forKey: "cities")
-      expect(storage.array(forKey: "cities") as? Array<String>) == array
-      
-      storage.removeKey("cities")
-      expect(storage.array(forKey: "cities")).to(beNil())
+    func testPersistsAndLoadsString() {
+        let str = "san francisco"
+        storage.setString(str, forKey: "city")
+        XCTAssertEqual(storage.string(forKey: "city"), str)
+        
+        storage.removeKey("city")
+        XCTAssertNil(storage.string(forKey: "city"))
     }
     
-    it("persists and loads dictionary") {
-      let dict = [
-        "san francisco": "tech",
-        "new york": "finance",
-        "paris": "fashion",
-      ]
-      storage.setDictionary(dict, forKey: "cityMap")
-      expect(storage.dictionary(forKey: "cityMap") as? Dictionary<String, String>) == dict
-      
-      storage.removeKey("cityMap")
-      expect(storage.dictionary(forKey: "cityMap")).to(beNil())
+    func testPersistsAndLoadsArray() {
+        let array = [
+          "san francisco",
+          "new york",
+          "tallinn",
+        ]
+        storage.setArray(array, forKey: "cities")
+        XCTAssertEqual(storage.array(forKey: "cities") as? Array<String>, array)
+        
+        storage.removeKey("cities")
+        XCTAssertNil(storage.array(forKey: "cities"))
     }
     
-    it("saves file to disk and removes from disk") {
-      let key = "input.txt"
-      let url = storage.url(forKey: key)
-      expect(try? url.checkResourceIsReachable()).to(beNil())
-      storage.setString("sloth", forKey: key)
-      expect(try! url.checkResourceIsReachable()) == true
-      storage.removeKey(key)
-      expect(try? url.checkResourceIsReachable()).to(beNil())
+    func testPersistsAndLoadsDictionary() {
+        let dict = [
+          "san francisco": "tech",
+          "new york": "finance",
+          "paris": "fashion",
+        ]
+        storage.setDictionary(dict, forKey: "cityMap")
+        XCTAssertEqual(storage.dictionary(forKey: "cityMap") as? Dictionary<String, String>, dict)
+        
+        storage.removeKey("cityMap")
+        XCTAssertNil(storage.dictionary(forKey: "cityMap"))
     }
     
-    it("should be binary compatible with old SDKs") {
-      let key = "traits.plist"
-      let dictIn = [
-        "san francisco": "tech",
-        "new york": "finance",
-        "paris": "fashion",
-      ]
-      
-      (dictIn as NSDictionary).write(to: storage.url(forKey: key), atomically: true)
-      let dictOut = storage.dictionary(forKey: key)
-      expect(dictOut as? [String: String]) == dictIn
+    func testSavesFileToDiskRemovesFromDisk() {
+        let key = "input.txt"
+        let url = storage.url(forKey: key)
+        XCTAssertNil(try? url.checkResourceIsReachable())
+        storage.setString("sloth", forKey: key)
+        XCTAssertEqual(try! url.checkResourceIsReachable(), true)
+        storage.removeKey(key)
+        XCTAssertNil(try? url.checkResourceIsReachable())
     }
     
-    it("should work with crypto") {
-      let url = FileStorage.applicationSupportDirectoryURL()
-      let crypto = AES256Crypto(password: "thetrees")
-      let s = FileStorage(folder: url!, crypto: crypto)
-      let dict = [
-        "san francisco": "tech",
-        "new york": "finance",
-        "paris": "fashion",
-      ]
-      s.setDictionary(dict, forKey: "cityMap")
-      expect(s.dictionary(forKey: "cityMap") as? Dictionary<String, String>) == dict
-      
-      s.removeKey("cityMap")
-      expect(s.dictionary(forKey: "cityMap")).to(beNil())
+    func testShouldBeBinaryCompatible() {
+        let key = "traits.plist"
+        let dictIn = [
+          "san francisco": "tech",
+          "new york": "finance",
+          "paris": "fashion",
+        ]
+        
+        (dictIn as NSDictionary).write(to: storage.url(forKey: key), atomically: true)
+        let dictOut = storage.dictionary(forKey: key)
+        XCTAssertEqual(dictOut as? [String: String], dictIn)
     }
     
-    afterEach {
-      storage.resetAll()
+    func testShouldWorkWithCrypto() {
+        let url = FileStorage.applicationSupportDirectoryURL()
+        let crypto = AES256Crypto(password: "thetrees")
+        let s = FileStorage(folder: url!, crypto: crypto)
+        let dict = [
+          "san francisco": "tech",
+          "new york": "finance",
+          "paris": "fashion",
+        ]
+        s.setDictionary(dict, forKey: "cityMap")
+        XCTAssertEqual(s.dictionary(forKey: "cityMap") as? Dictionary<String, String>, dict)
+        
+        s.removeKey("cityMap")
+        XCTAssertNil(s.dictionary(forKey: "cityMap"))
     }
-  }
 }
diff --git a/AnalyticsTests/HTTPClientTest.swift b/AnalyticsTests/HTTPClientTest.swift
index 9ae2ab64b..c377df191 100644
--- a/AnalyticsTests/HTTPClientTest.swift
+++ b/AnalyticsTests/HTTPClientTest.swift
@@ -6,260 +6,248 @@
 //  Copyright © 2016 Segment. All rights reserved.
 //
 
-import Quick
-import Nimble
 import Nocilla
 import Analytics
+import XCTest
 
-class HTTPClientTest: QuickSpec {
-  override func spec() {
-
+class HTTPClientTest: XCTestCase {
+    
     var client: HTTPClient!
-
-    beforeEach {
-      LSNocilla.sharedInstance().start()
-      client = HTTPClient(requestFactory: nil)
+    let batch: [String: Any] = ["sentAt":"2016-07-19'T'19:25:06Z", "batch":[["type":"track", "event":"foo"]]]
+    let context: [String: Any] = [
+        "os": [
+            "name": "iPhone OS",
+            "version" : "8.1.3",
+        ],
+        "ip": "8.8.8.8",
+    ]
+    
+    override func setUp() {
+        super.setUp()
+        LSNocilla.sharedInstance().start()
+        client = HTTPClient(requestFactory: nil)
     }
-    afterEach {
-      LSNocilla.sharedInstance().clearStubs()
-      LSNocilla.sharedInstance().stop()
+    
+    override func tearDown() {
+        super.tearDown()
+        LSNocilla.sharedInstance().clearStubs()
+        LSNocilla.sharedInstance().stop()
     }
-
-    describe("defaultRequestFactory") {
-      it("preserves url") {
+    
+    func testDefaultRequestFactor() {
         let factory = HTTPClient.defaultRequestFactory()
         let url = URL(string: "https://api.segment.io/v1/batch")
         let request = factory(url!)
-        expect(request.url) == url
-      }
+        XCTAssertEqual(request.url, url, "URLs should be the same")
     }
-
-    describe("settingsForWriteKey") {
-      it("succeeds for 2xx response") {
+    
+    func testSettingsForWriteKeySucceeds2xx() {
         _ = stubRequest("GET", "https://cdn-settings.segment.com/v1/projects/foo/settings" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withHeaders(["Accept-Encoding" : "gzip" ])!
-          .andReturn(200)!
-          .withHeaders(["Content-Type" : "application/json"])!
-          .withBody("{\"integrations\":{\"Segment.io\":{\"apiKey\":\"foo\"}},\"plan\":{\"track\":{}}}" as NSString)
-
-        var done = false
-        let task = client.settings(forWriteKey: "foo", completionHandler: { success, settings in
-          expect(success) == true
-          expect(settings as NSDictionary?) == [
-            "integrations": [
-              "Segment.io": [
-                "apiKey":"foo"
-              ]
-            ],
-            "plan":[
-              "track": [:]
-            ]
-          ] as NSDictionary
-          done = true
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withHeaders(["Accept-Encoding" : "gzip" ])!
+            .andReturn(200)!
+            .withHeaders(["Content-Type" : "application/json"])!
+            .withBody("{\"integrations\":{\"Segment.io\":{\"apiKey\":\"foo\"}},\"plan\":{\"track\":{}}}" as NSString)
+        
+        let doneExpectation = expectation(description: "Done with url session task")
+        
+        _ = client.settings(forWriteKey: "foo", completionHandler: { success, settings in
+            
+            XCTAssert(success, "Should be successful")
+            XCTAssertEqual(settings as NSDictionary?, [
+                "integrations": [
+                    "Segment.io": [
+                        "apiKey":"foo"
+                    ]
+                ],
+                "plan":[
+                    "track": [:]
+                ]
+                ] as NSDictionary)
+            doneExpectation.fulfill()
         })
-        expect(task.state).toEventually(equal(URLSessionTask.State.completed))
-        expect(done).toEventually(beTrue())
-      }
-
-      it("fails for non 2xx response") {
+        
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testSettingsWriteKey2xxResponse() {
         _ = stubRequest("GET", "https://cdn-settings.segment.com/v1/projects/foo/settings" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withHeaders(["Accept-Encoding" : "gzip" ])!
-          .andReturn(400)!
-          .withHeaders(["Content-Type" : "application/json" ])!
-          .withBody("{\"integrations\":{\"Segment.io\":{\"apiKey\":\"foo\"}},\"plan\":{\"track\":{}}}" as NSString)
-        var done = false
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withHeaders(["Accept-Encoding" : "gzip" ])!
+            .andReturn(400)!
+            .withHeaders(["Content-Type" : "application/json" ])!
+            .withBody("{\"integrations\":{\"Segment.io\":{\"apiKey\":\"foo\"}},\"plan\":{\"track\":{}}}" as NSString)
+        
+        let doneExpectation = expectation(description: "Done with url session task")
+        
         client.settings(forWriteKey: "foo", completionHandler: { success, settings in
-          expect(success) == false
-          expect(settings).to(beNil())
-          done = true
+            XCTAssertFalse(success, "Success should be false")
+            XCTAssertNil(settings, "Failure should have nil settings")
+            
+            doneExpectation.fulfill()
         })
-        expect(done).toEventually(beTrue())
-      }
-
-      it("fails for json error") {
+        
+        wait(for: [doneExpectation], timeout: 1.0)
+        
+    }
+    
+    func testSettingsWriteKey2xxJSONErrorResponse() {
         _ = stubRequest("GET", "https://cdn-settings.segment.com/v1/projects/foo/settings" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withHeaders(["Accept-Encoding":"gzip"])!
-          .andReturn(200)!
-          .withHeaders(["Content-Type":"application/json"])!
-          .withBody("{\"integrations" as NSString)
-
-        var done = false
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withHeaders(["Accept-Encoding":"gzip"])!
+            .andReturn(200)!
+            .withHeaders(["Content-Type":"application/json"])!
+            .withBody("{\"integrations" as NSString)
+        
+        let doneExpectation = expectation(description: "Done with url session task")
+        
         client.settings(forWriteKey: "foo", completionHandler: { success, settings in
-          expect(success) == false
-          expect(settings).to(beNil())
-          done = true
+            XCTAssertFalse(success, "Success should be false")
+            XCTAssertNil(settings, "Failure should have nil settings")
+            doneExpectation.fulfill()
         })
-        expect(done).toEventually(beTrue())
-      }
+        wait(for: [doneExpectation], timeout: 1.0)
     }
-
-    describe("upload") {
-      it("does not ask to retry for json error") {
+    
+    func testUploadNoRetry() {
         let batch: [String: Any] = [
-          // Dates cannot be serialized as is so the json serialzation will fail.
-          "sentAt": NSDate(),
-          "batch": [["type": "track", "event": "foo"]],
+            // Dates cannot be serialized as is so the json serialzation will fail.
+            "sentAt": NSDate(),
+            "batch": [["type": "track", "event": "foo"]],
         ]
-        var done = false
+        let doneExpectation = expectation(description: "Done with url session task")
         let task = client.upload(batch, forWriteKey: "bar") { retry in
-          expect(retry) == false
-          done = true
+            XCTAssertFalse(retry, "Retry should be false")
+            doneExpectation.fulfill()
         }
-        expect(task).to(beNil())
-        expect(done).toEventually(beTrue())
-      }
-
-      let batch: [String: Any] = ["sentAt":"2016-07-19'T'19:25:06Z", "batch":[["type":"track", "event":"foo"]]]
-
-
-      it("does not ask to retry for 2xx response") {
+        XCTAssertNil(task, "Task should be nil")
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testUploadNoRetry2xx() {
         _ = stubRequest("POST", "https://api.segment.io/v1/batch" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withJsonGzippedBody(batch as AnyObject)
-          .withWriteKey("bar")
-          .andReturn(200)
-        var done = false
-        let task = client.upload(batch, forWriteKey: "bar") { retry in
-          expect(retry) == false
-          done = true
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withJsonGzippedBody(batch as AnyObject)
+            .withWriteKey("bar")
+            .andReturn(200)
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.upload(batch, forWriteKey: "bar") { retry in
+            XCTAssertFalse(retry, "Retry should be false")
+            doneExpectation.fulfill()
         }
-        expect(done).toEventually(beTrue())
-        expect(task?.state).toEventually(equal(URLSessionTask.State.completed))
-      }
-
-      it("asks to retry for 3xx response") {
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testUploadRetry3xx() {
         _ = stubRequest("POST", "https://api.segment.io/v1/batch" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withJsonGzippedBody(batch as AnyObject)
-          .withWriteKey("bar")
-          .andReturn(304)
-        var done = false
-        let task = client.upload(batch, forWriteKey: "bar") { retry in
-          expect(retry) == true
-          done = true
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withJsonGzippedBody(batch as AnyObject)
+            .withWriteKey("bar")
+            .andReturn(304)
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.upload(batch, forWriteKey: "bar") { retry in
+            XCTAssert(retry, "Retry should be true")
+            doneExpectation.fulfill()
         }
-        expect(done).toEventually(beTrue())
-        expect(task?.state).toEventually(equal(URLSessionTask.State.completed))
-      }
-
-      it("does not ask to retry for 4xx response") {
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testUploadNoRetry4xx() {
         _ = stubRequest("POST", "https://api.segment.io/v1/batch" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withJsonGzippedBody(batch as AnyObject)
-          .withWriteKey("bar")
-          .andReturn(401)
-        var done = false
-        let task = client.upload(batch, forWriteKey: "bar") { retry in
-          expect(retry) == false
-          done = true
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withJsonGzippedBody(batch as AnyObject)
+            .withWriteKey("bar")
+            .andReturn(401)
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.upload(batch, forWriteKey: "bar") { retry in
+            XCTAssertFalse(retry, "Retry should be false")
+            doneExpectation.fulfill()
         }
-        expect(done).toEventually(beTrue())
-        expect(task?.state).toEventually(equal(URLSessionTask.State.completed))
-      }
-
-      it("asks to retry for 429 response") {
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testRetryFor429() {
         _ = stubRequest("POST", "https://api.segment.io/v1/batch" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withJsonGzippedBody(batch as AnyObject)
-          .withWriteKey("bar")
-          .andReturn(429)
-        var done = false
-        let task = client.upload(batch, forWriteKey: "bar") { retry in
-          expect(retry) == true
-          done = true
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withJsonGzippedBody(batch as AnyObject)
+            .withWriteKey("bar")
+            .andReturn(429)
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.upload(batch, forWriteKey: "bar") { retry in
+            XCTAssert(retry, "Retry should be true")
+            doneExpectation.fulfill()
         }
-        expect(done).toEventually(beTrue())
-        expect(task?.state).toEventually(equal(URLSessionTask.State.completed))
-      }
-
-      it("asks to retry for 5xx response") {
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testRetryFor5xx() {
         _ = stubRequest("POST", "https://api.segment.io/v1/batch" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withJsonGzippedBody(batch as AnyObject)
-          .withWriteKey("bar")
-          .andReturn(504)
-        var done = false
-        let task = client.upload(batch, forWriteKey: "bar") { retry in
-          expect(retry) == true
-          done = true
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withJsonGzippedBody(batch as AnyObject)
+            .withWriteKey("bar")
+            .andReturn(504)
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.upload(batch, forWriteKey: "bar") { retry in
+            XCTAssert(retry, "Retry should be true")
+            doneExpectation.fulfill()
         }
-        expect(done).toEventually(beTrue())
-        expect(task?.state).toEventually(equal(URLSessionTask.State.completed))
-      }
-
-      it("fails when batch size exceeds the max limit size") {
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testBatchSizeFailure() {
         let oversizedBatch: [String: Any] = ["sentAt":"2016-07-19'T'19:25:06Z",
                                              "batch": Array(repeating: ["type":"track", "event":"foo"], count: 16000)]
-        var done = false
-        let task = client.upload(oversizedBatch, forWriteKey: "bar") { retry in
-          expect(retry) == false
-          done = true
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.upload(oversizedBatch, forWriteKey: "bar") { retry in
+            XCTAssertFalse(retry, "Retry should be false")
+            doneExpectation.fulfill()
         }
-        expect(done).toEventually(beTrue())
-        expect(task).toEventually(beNil())
-      }
+        wait(for: [doneExpectation], timeout: 1.0)
     }
-
-    describe("attribution") {
-      it("fails for json error") {
+    
+    func testAttributionFailsForJSONError() {
         let device = [
-          // Dates cannot be serialized as is so the json serialzation will fail.
-          "sentAt": NSDate(),
+            // Dates cannot be serialized as is so the json serialzation will fail.
+            "sentAt": NSDate(),
         ]
-        var done = false
-        let task = client.attribution(withWriteKey: "bar", forDevice: device) { success, properties in
-          expect(success) == false
-          done = true
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.attribution(withWriteKey: "bar", forDevice: device) { success, properties in
+            XCTAssertFalse(success, "Retry should be false")
+            doneExpectation.fulfill()
         }
-        expect(task).to(beNil())
-        expect(done).toEventually(beTrue())
-      }
-
-      let context: [String: Any] = [
-        "os": [
-          "name": "iPhone OS",
-          "version" : "8.1.3",
-        ],
-        "ip": "8.8.8.8",
-      ]
-
-      it("succeeds for 2xx response") {
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testAttributionSucceedsFor2xx() {
         _ = stubRequest("POST", "https://mobile-service.segment.com/v1/attribution" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withWriteKey("foo")
-          .andReturn(200)!
-          .withBody("{\"provider\": \"mock\"}" as NSString)
-
-        var done = false
-        let task = client.attribution(withWriteKey: "foo", forDevice: context) { success, properties in
-          expect(success) == true
-          expect(properties as? [String: String]) == [
-            "provider": "mock"
-          ]
-          done = true
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withWriteKey("foo")
+            .andReturn(200)!
+            .withBody("{\"provider\": \"mock\"}" as NSString)
+        
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.attribution(withWriteKey: "foo", forDevice: context) { success, properties in
+            XCTAssert(success, "Success should be true")
+            XCTAssertEqual(properties as? [String: String], [
+                "provider": "mock"
+            ])
+            doneExpectation.fulfill()
         }
-        expect(task.state).toEventually(equal(URLSessionTask.State.completed))
-        expect(done).toEventually(beTrue())
-      }
-
-      it("fails for non 2xx response") {
+        wait(for: [doneExpectation], timeout: 1.0)
+    }
+    
+    func testAttributionFailsForNon2xx() {
         _ = stubRequest("POST", "https://mobile-service.segment.com/v1/attribution" as NSString)
-          .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
-          .withWriteKey("foo")
-          .andReturn(404)!
-          .withBody("not found" as NSString)
-        var done = false
-        let task = client.attribution(withWriteKey: "foo", forDevice: context) { success, properties in
-          expect(success) == false
-          expect(properties).to(beNil())
-          done = true
+            .withHeader("User-Agent", "analytics-ios/" + Analytics.version())!
+            .withWriteKey("foo")
+            .andReturn(404)!
+            .withBody("not found" as NSString)
+        let doneExpectation = expectation(description: "Done with url session task")
+        _ = client.attribution(withWriteKey: "foo", forDevice: context) { success, properties in
+            XCTAssertFalse(success, "Success should be false")
+            XCTAssertNil(properties, "Properties should be nil")
+            doneExpectation.fulfill()
         }
-        expect(task.state).toEventually(equal(URLSessionTask.State.completed))
-        expect(done).toEventually(beTrue())
-      }
+        wait(for: [doneExpectation], timeout: 1.0)
     }
-  }
 }
diff --git a/AnalyticsTests/IntegrationsManagerTest.swift b/AnalyticsTests/IntegrationsManagerTest.swift
index 4fa2700d2..25a43f7c7 100644
--- a/AnalyticsTests/IntegrationsManagerTest.swift
+++ b/AnalyticsTests/IntegrationsManagerTest.swift
@@ -1,106 +1,97 @@
 import Analytics
-import Quick
-import Nimble
 import SwiftTryCatch
-
-class IntegrationsManagerTest: QuickSpec {
-  
-  override func spec() {
-    describe("IntegrationsManager") {
-      context("is track event enabled for integration in plan") {
-        
-        it("valid value types are used in integration enablement flags") {
-          var exception: NSException? = nil
-          SwiftTryCatch.tryRun({
+import XCTest
+class IntegrationsManagerTest: XCTestCase {
+    
+    func testValidValueTypesInIntegrationEnablementFlags() {
+        var exception: NSException? = nil
+        SwiftTryCatch.tryRun({
             IntegrationsManager.isIntegration("comScore", enabledInOptions: ["comScore": ["blah": 1]])
             IntegrationsManager.isIntegration("comScore", enabledInOptions: ["comScore": true])
-          }, catchRun: { e in
+        }, catchRun: { e in
             exception = e
-          }, finallyRun: nil)
-          
-          expect(exception).to(beNil())
-        }
+        }, finallyRun: nil)
         
-        it("asserts when invalid value types are used integration enablement flags") {
-          var exception: NSException? = nil
-          SwiftTryCatch.tryRun({
+        XCTAssertNil(exception)
+    }
+    
+    func testAssertsWhenInvalidValueTypesUsedIntegrationEnablement() {
+        var exception: NSException? = nil
+        SwiftTryCatch.tryRun({
             IntegrationsManager.isIntegration("comScore", enabledInOptions: ["comScore": "blah"])
-          }, catchRun: { e in
+        }, catchRun: { e in
             exception = e
-          }, finallyRun: nil)
-          
-          expect(exception).toNot(beNil())
-        }
+        }, finallyRun: nil)
         
-        it("asserts when invalid value types are used integration enablement flags") {
-          var exception: NSException? = nil
-          SwiftTryCatch.tryRun({
+        XCTAssertNotNil(exception)
+    }
+    
+    func testAssertsWhenInvalidValueTypesIntegrationEnableFlags() {
+        var exception: NSException? = nil
+        SwiftTryCatch.tryRun({
             // we don't accept array's as values.
             IntegrationsManager.isIntegration("comScore", enabledInOptions: ["comScore": ["key", 1]])
-          }, catchRun: { e in
+        }, catchRun: { e in
             exception = e
-          }, finallyRun: nil)
-          
-          expect(exception).toNot(beNil())
-        }
-        
-        it("pulls valid integration data when supplied") {
-          let enabled = IntegrationsManager.isIntegration("comScore", enabledInOptions: ["comScore": true])
-          expect(enabled).to(beTrue())
-        }
-
-        it("falls back correctly when values aren't explicitly specified") {
-          let enabled = IntegrationsManager.isIntegration("comScore", enabledInOptions: ["all": true])
-          expect(enabled).to(beTrue())
-          let allEnabled = IntegrationsManager.isIntegration("comScore", enabledInOptions: ["All": true])
-          expect(allEnabled).to(beTrue())
-        }
-
-        it("returns true when there is no plan") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Amplitude", inPlan:[:])
-          expect(enabled).to(beTrue())
-        }
-        
-        it("returns true when plan is empty") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":[:]])
-          expect(enabled).to(beTrue())
-        }
-        
-        it("returns true when plan enables event") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["hello world":["enabled":true]]])
-          expect(enabled).to(beTrue())
-        }
-        
-        it("returns false when plan disables event") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Amplitude", inPlan:["track":["hello world":["enabled":false]]])
-          expect(enabled).to(beFalse())
-        }
-        
-        it("returns true for Segment integration even when plan disables event") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Segment.io", inPlan:["track":["hello world":["enabled":false]]])
-          expect(enabled).to(beTrue())
-        }
-        
-        it("returns true when plan enables event for integration") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["hello world":["enabled":true, "integrations":["Mixpanel":true]]]])
-          expect(enabled).to(beTrue())
-        }
-        
-        it("returns false when plan disables event for integration") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["hello world":["enabled":true, "integrations":["Mixpanel":false]]]])
-          expect(enabled).to(beFalse())
-        }
-        
-        it("returns false when plan disables new events by default") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["__default":["enabled":false]]])
-          expect(enabled).to(beFalse())
-        }
+        }, finallyRun: nil)
         
-        it("returns uses event plan rather over defaults") {
-          let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["__default":["enabled":false],"hello world":["enabled":true]]])
-          expect(enabled).to(beTrue())
-        }
-      }
+        XCTAssertNotNil(exception)
+    }
+    
+    func testPullsValidIntegrationDataWhenSupplied() {
+        let enabled = IntegrationsManager.isIntegration("comScore", enabledInOptions: ["comScore": true])
+        XCTAssert(enabled)
+    }
+    
+    func testFallsBackCorrectlyWhenNotSpecified() {
+        let enabled = IntegrationsManager.isIntegration("comScore", enabledInOptions: ["all": true])
+        XCTAssert(enabled)
+        let allEnabled = IntegrationsManager.isIntegration("comScore", enabledInOptions: ["All": true])
+        XCTAssert(allEnabled)
+    }
+    
+    func testReturnsTrueWhenThereisNoPlan() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Amplitude", inPlan:[:])
+        XCTAssert(enabled)
+    }
+    
+    func testReturnsTrueWhenPlanIsEmpty() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":[:]])
+        XCTAssert(enabled)
+    }
+    
+    func testReturnsTrueWhenPlanEnablesEvent() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["hello world":["enabled":true]]])
+        XCTAssert(enabled)
+    }
+    
+    func testReturnsFalseWhenPlanDisablesEvent() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Amplitude", inPlan:["track":["hello world":["enabled":false]]])
+        XCTAssertFalse(enabled)
+    }
+    
+    func testReturnsTrueForSegmentIntegrationWhenDisablesEvent() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Segment.io", inPlan:["track":["hello world":["enabled":false]]])
+        XCTAssert(enabled)
+    }
+    
+    func testReturnsTrueWhenPlanEnablesEventForIntegration() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["hello world":["enabled":true, "integrations":["Mixpanel":true]]]])
+        XCTAssert(enabled)
+    }
+    
+    func testReturnsFalseWhenPlanDisablesEventForIntegration() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["hello world":["enabled":true, "integrations":["Mixpanel":false]]]])
+        XCTAssertFalse(enabled)
+    }
+    
+    func testReturnsFalseWhenPlanDisablesNewEvents() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["__default":["enabled":false]]])
+        XCTAssertFalse(enabled)
+    }
+    
+    func testReturnsUsesEventPlanRatherOverDefaults() {
+        let enabled = IntegrationsManager.isTrackEvent("hello world", enabledForIntegration: "Mixpanel", inPlan:["track":["__default":["enabled":false],"hello world":["enabled":true]]])
+        XCTAssert(enabled)
     }
-  }
 }
diff --git a/AnalyticsTests/MiddlewareTests.swift b/AnalyticsTests/MiddlewareTests.swift
index f02a85ebb..14f3ca977 100644
--- a/AnalyticsTests/MiddlewareTests.swift
+++ b/AnalyticsTests/MiddlewareTests.swift
@@ -7,154 +7,141 @@
 //
 
 
-import Quick
-import Nimble
 import Analytics
+import XCTest
 
 // Changing event names and adding custom attributes
 let customizeAllTrackCalls = BlockMiddleware { (context, next) in
-  if context.eventType == .track {
-    next(context.modify { ctx in
-      guard let track = ctx.payload as? TrackPayload else {
-        return
-      }
-      let newEvent = "[New] \(track.event)"
-      var newProps = track.properties ?? [:]
-      newProps["customAttribute"] = "Hello"
-      newProps["nullTest"] = NSNull()
-      ctx.payload = TrackPayload(
-        event: newEvent,
-        properties: newProps,
-        context: track.context,
-        integrations: track.integrations
-      )
-    })
-  } else {
-    next(context)
-  }
+    if context.eventType == .track {
+        next(context.modify { ctx in
+            guard let track = ctx.payload as? TrackPayload else {
+                return
+            }
+            let newEvent = "[New] \(track.event)"
+            var newProps = track.properties ?? [:]
+            newProps["customAttribute"] = "Hello"
+            newProps["nullTest"] = NSNull()
+            ctx.payload = TrackPayload(
+                event: newEvent,
+                properties: newProps,
+                context: track.context,
+                integrations: track.integrations
+            )
+        })
+    } else {
+        next(context)
+    }
 }
 
 // Simply swallows all calls and does not pass events downstream
 let eatAllCalls = BlockMiddleware { (context, next) in
 }
 
-class SourceMiddlewareTests: QuickSpec {
-  override func spec() {
-    it("receives events") {
-      let config = AnalyticsConfiguration(writeKey: "TESTKEY")
-      let passthrough = PassthroughMiddleware()
-      config.sourceMiddleware = [
-        passthrough,
-      ]
-      let analytics = Analytics(configuration: config)
-      analytics.identify("testUserId1")
-      expect(passthrough.lastContext?.eventType) == EventType.identify
-      let identify = passthrough.lastContext?.payload as? IdentifyPayload
-      expect(identify?.userId) == "testUserId1"
+class SourceMiddlewareTests: XCTestCase {
+    
+    func testReceivesEvents() {
+        let config = AnalyticsConfiguration(writeKey: "TESTKEY")
+        let passthrough = PassthroughMiddleware()
+        config.sourceMiddleware = [
+            passthrough,
+        ]
+        let analytics = Analytics(configuration: config)
+        analytics.identify("testUserId1")
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.identify)
+        let identify = passthrough.lastContext?.payload as? IdentifyPayload
+        XCTAssertEqual(identify?.userId, "testUserId1")
     }
     
-    it("modifies and passes event to next") {
-      let config = AnalyticsConfiguration(writeKey: "TESTKEY")
-      let passthrough = PassthroughMiddleware()
-      config.sourceMiddleware = [
-        customizeAllTrackCalls,
-        passthrough,
-      ]
-      let analytics = Analytics(configuration: config)
-      analytics.track("Purchase Success")
-      expect(passthrough.lastContext?.eventType) == EventType.track
-      let track = passthrough.lastContext?.payload as? TrackPayload
-      expect(track?.event) == "[New] Purchase Success"
-      expect(track?.properties?["customAttribute"] as? String) == "Hello"
-      let isNull = (track?.properties?["nullTest"] is NSNull)
-      expect(isNull) == true
+    func testModifiesAndPassesEventToNext() {
+        let config = AnalyticsConfiguration(writeKey: "TESTKEY")
+        let passthrough = PassthroughMiddleware()
+        config.sourceMiddleware = [
+            customizeAllTrackCalls,
+            passthrough,
+        ]
+        let analytics = Analytics(configuration: config)
+        analytics.track("Purchase Success")
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.track)
+        let track = passthrough.lastContext?.payload as? TrackPayload
+        XCTAssertEqual(track?.event, "[New] Purchase Success")
+        XCTAssertEqual(track?.properties?["customAttribute"] as? String, "Hello")
+        let isNull = (track?.properties?["nullTest"] is NSNull)
+        XCTAssert(isNull)
     }
     
-    it("expects event to be swallowed if next is not called") {
-      let config = AnalyticsConfiguration(writeKey: "TESTKEY")
-      let passthrough = PassthroughMiddleware()
-      config.sourceMiddleware = [
-        eatAllCalls,
-        passthrough,
-      ]
-      let analytics = Analytics(configuration: config)
-      analytics.track("Purchase Success")
-      expect(passthrough.lastContext).to(beNil())
+    func testExpectsEventToBeSwallowed() {
+        let config = AnalyticsConfiguration(writeKey: "TESTKEY")
+        let passthrough = PassthroughMiddleware()
+        config.sourceMiddleware = [
+            eatAllCalls,
+            passthrough,
+        ]
+        let analytics = Analytics(configuration: config)
+        analytics.track("Purchase Success")
+        XCTAssertNil(passthrough.lastContext)
     }
-  }
 }
 
-class IntegrationMiddlewareTests: QuickSpec {
-  override func spec() {
-    it("receives events") {
-      let config = AnalyticsConfiguration(writeKey: "TESTKEY")
-      let passthrough = PassthroughMiddleware()
-      config.destinationMiddleware = [DestinationMiddleware(key: SegmentIntegrationFactory().key(), middleware: [passthrough])]
-      let analytics = Analytics(configuration: config)
-      analytics.identify("testUserId1")
-      
-      // pump the runloop until we have a last context.
-      // integration middleware is held up until initialization is completed.
-      waitUntil(timeout: 60) { done in
-        let queue = DispatchQueue(label: "test")
-        queue.async {
-          while(passthrough.lastContext == nil) {
+class IntegrationMiddlewareTests: XCTestCase {
+    
+    func testReceivesEvents() {
+        let config = AnalyticsConfiguration(writeKey: "TESTKEY")
+        let passthrough = PassthroughMiddleware()
+        config.destinationMiddleware = [DestinationMiddleware(key: SegmentIntegrationFactory().key(), middleware: [passthrough])]
+        let analytics = Analytics(configuration: config)
+        analytics.identify("testUserId1")
+        
+        // pump the runloop until we have a last context.
+        // integration middleware is held up until initialization is completed.
+        let currentTime = Date()
+        while(passthrough.lastContext == nil && currentTime < currentTime + 60) {
             sleep(1);
-          }
-          done()
         }
-      }
-      
-      expect(passthrough.lastContext?.eventType) == EventType.identify
-      let identify = passthrough.lastContext?.payload as? IdentifyPayload
-      expect(identify?.userId) == "testUserId1"
+        
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.identify)
+        let identify = passthrough.lastContext?.payload as? IdentifyPayload
+        XCTAssertEqual(identify?.userId, "testUserId1")
     }
     
-    it("modifies and passes event to next") {
-      let config = AnalyticsConfiguration(writeKey: "TESTKEY")
-      let passthrough = PassthroughMiddleware()
-      config.destinationMiddleware = [DestinationMiddleware(key: SegmentIntegrationFactory().key(), middleware: [customizeAllTrackCalls, passthrough])]
-      let analytics = Analytics(configuration: config)
-      analytics.track("Purchase Success")
-      
-      // pump the runloop until we have a last context.
-      // integration middleware is held up until initialization is completed.
-      waitUntil(timeout: 60) { done in
-        let queue = DispatchQueue(label: "test")
-        queue.async {
-          while(passthrough.lastContext == nil) {
+    func testModifiesAndPassesEventToNext() {
+        let config = AnalyticsConfiguration(writeKey: "TESTKEY")
+        let passthrough = PassthroughMiddleware()
+        config.destinationMiddleware = [DestinationMiddleware(key: SegmentIntegrationFactory().key(), middleware: [customizeAllTrackCalls, passthrough])]
+        let analytics = Analytics(configuration: config)
+        analytics.track("Purchase Success")
+        
+        // pump the runloop until we have a last context.
+        // integration middleware is held up until initialization is completed.
+        let currentTime = Date()
+        while(passthrough.lastContext == nil && currentTime < currentTime + 60) {
             sleep(1)
-          }
-          done()
         }
-      }
-      
-      expect(passthrough.lastContext?.eventType) == EventType.track
-      let track = passthrough.lastContext?.payload as? TrackPayload
-      expect(track?.event) == "[New] Purchase Success"
-      expect(track?.properties?["customAttribute"] as? String) == "Hello"
-      let isNull = (track?.properties?["nullTest"] is NSNull)
-      expect(isNull) == true
+        
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.track)
+        let track = passthrough.lastContext?.payload as? TrackPayload
+        XCTAssertEqual(track?.event, "[New] Purchase Success")
+        XCTAssertEqual(track?.properties?["customAttribute"] as? String, "Hello")
+        let isNull = (track?.properties?["nullTest"] is NSNull)
+        XCTAssert(isNull, "Should be true")
     }
     
-    it("expects event to be swallowed if next is not called") {
-      // Since we're testing that an event is dropped, the previously used run loop pump won't work here.
-      var initialized = false
-      NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: SEGAnalyticsIntegrationDidStart), object: nil, queue: nil) { (notification) in
-        initialized = true
-      }
+    func testExpectsEventToBeSwallowedIfOtherIsNotCalled() {
+        // Since we're testing that an event is dropped, the previously used run loop pump won't work here.
+        var initialized = false
+        NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: SEGAnalyticsIntegrationDidStart), object: nil, queue: nil) { (notification) in
+            initialized = true
+        }
         
-      let config = AnalyticsConfiguration(writeKey: "TESTKEY")
-      let passthrough = PassthroughMiddleware()
-      config.destinationMiddleware = [DestinationMiddleware(key: SegmentIntegrationFactory().key(), middleware: [eatAllCalls, passthrough])]
-      let analytics = Analytics(configuration: config)
-      analytics.track("Purchase Success")
-
-      while (!initialized) {
-        sleep(1)
-      }
-
-      expect(passthrough.lastContext).to(beNil())
+        let config = AnalyticsConfiguration(writeKey: "TESTKEY")
+        let passthrough = PassthroughMiddleware()
+        config.destinationMiddleware = [DestinationMiddleware(key: SegmentIntegrationFactory().key(), middleware: [eatAllCalls, passthrough])]
+        let analytics = Analytics(configuration: config)
+        analytics.track("Purchase Success")
+        
+        while (!initialized) {
+            sleep(1)
+        }
+        
+        XCTAssertNil(passthrough.lastContext)
     }
-  }
 }
diff --git a/AnalyticsTests/StoreKitTrackerTests.swift b/AnalyticsTests/StoreKitTrackerTests.swift
index cee21c01c..5be94b17b 100644
--- a/AnalyticsTests/StoreKitTrackerTests.swift
+++ b/AnalyticsTests/StoreKitTrackerTests.swift
@@ -6,9 +6,8 @@
 //  Copyright © 2016 Segment. All rights reserved.
 //
 
-import Quick
-import Nimble
 import Analytics
+import XCTest
 
 class mockTransaction: SKPaymentTransaction {
   override var transactionIdentifier: String? {
@@ -39,33 +38,30 @@ class mockProductResponse: SKProductsResponse {
   }
 }
 
-class StoreKitTrackerTests: QuickSpec {
-  override func spec() {
+class StoreKitTrackerTests: XCTestCase {
 
     var test: TestMiddleware!
     var tracker: StoreKitTracker!
     var analytics: Analytics!
-
-    beforeEach {
-      let config = AnalyticsConfiguration(writeKey: "foobar")
-      test = TestMiddleware()
-      config.sourceMiddleware = [test]
-      analytics = Analytics(configuration: config)
-      tracker = StoreKitTracker.trackTransactions(for: analytics)
+    
+    override func setUp() {
+        super.setUp()
+        let config = AnalyticsConfiguration(writeKey: "foobar")
+        test = TestMiddleware()
+        config.sourceMiddleware = [test]
+        analytics = Analytics(configuration: config)
+        tracker = StoreKitTracker.trackTransactions(for: analytics)
     }
-
-    it("SKPaymentQueue Observer") {
-      let transaction = mockTransaction()
-      expect(transaction.transactionIdentifier) == "tid"
-      tracker.paymentQueue(SKPaymentQueue(), updatedTransactions: [transaction])
-      
-      tracker.productsRequest(SKProductsRequest(), didReceive: mockProductResponse())
-      
-      let payload = test.lastContext?.payload as? TrackPayload
-      
-      expect(payload?.event) == "Order Completed"
+    
+    func testSKPaymentQueueObserver() {
+        let transaction = mockTransaction()
+        XCTAssertEqual(transaction.transactionIdentifier, "tid")
+        tracker.paymentQueue(SKPaymentQueue(), updatedTransactions: [transaction])
+        
+        tracker.productsRequest(SKProductsRequest(), didReceive: mockProductResponse())
+        
+        let payload = test.lastContext?.payload as? TrackPayload
+        
+        XCTAssertEqual(payload?.event, "Order Completed")
     }
-
-  }
-
 }
diff --git a/AnalyticsTests/TrackingTests.swift b/AnalyticsTests/TrackingTests.swift
index 571445efa..dfeb3ae8e 100644
--- a/AnalyticsTests/TrackingTests.swift
+++ b/AnalyticsTests/TrackingTests.swift
@@ -7,103 +7,102 @@
 //
 
 
-import Quick
-import Nimble
 import Analytics
+import XCTest
 
-class TrackingTests: QuickSpec {
-  override func spec() {
+class TrackingTests: XCTestCase {
+    
     var passthrough: PassthroughMiddleware!
     var analytics: Analytics!
-
-    beforeEach {
-      let config = AnalyticsConfiguration(writeKey: "QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE")
-      passthrough = PassthroughMiddleware()
-      config.sourceMiddleware = [
-        passthrough,
-      ]
-      analytics = Analytics(configuration: config)
+    
+    override func setUp() {
+        super.setUp()
+        let config = AnalyticsConfiguration(writeKey: "QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE")
+        passthrough = PassthroughMiddleware()
+        config.sourceMiddleware = [
+            passthrough,
+        ]
+        analytics = Analytics(configuration: config)
     }
-
-    afterEach {
-      analytics.reset()
+    
+    override func tearDown() {
+        super.tearDown()
+        analytics.reset()
     }
-
-    it("handles identify:") {
-      analytics.identify("testUserId1", traits: [
-        "firstName": "Peter"
-      ])
-        expect(passthrough.lastContext?.eventType) == EventType.identify
-      let identify = passthrough.lastContext?.payload as? IdentifyPayload
-      expect(identify?.userId) == "testUserId1"
-      expect(identify?.anonymousId).toNot(beNil())
-      expect(identify?.traits?["firstName"] as? String) == "Peter"
+    
+    func testHandlesIdentify() {
+        analytics.identify("testUserId1", traits: [
+            "firstName": "Peter"
+        ])
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.identify)
+        let identify = passthrough.lastContext?.payload as? IdentifyPayload
+        XCTAssertEqual(identify?.userId, "testUserId1")
+        XCTAssertNotNil(identify?.anonymousId)
+        XCTAssertEqual(identify?.traits?["firstName"] as? String, "Peter")
     }
-
-    it("handles identify with custom anonymousId:") {
-      analytics.identify("testUserId1", traits: [
-        "firstName": "Peter"
-        ], options: [
-          "anonymousId": "a_custom_anonymous_id"
+    
+    func testHandlesIdentifyWithCustomAnonymousId() {
+        analytics.identify("testUserId1", traits: [
+            "firstName": "Peter"
+            ], options: [
+                "anonymousId": "a_custom_anonymous_id"
         ])
-      expect(passthrough.lastContext?.eventType) == EventType.identify
-      let identify = passthrough.lastContext?.payload as? IdentifyPayload
-      expect(identify?.userId) == "testUserId1"
-      expect(identify?.anonymousId) == "a_custom_anonymous_id"
-      expect(identify?.traits?["firstName"] as? String) == "Peter"
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.identify)
+        let identify = passthrough.lastContext?.payload as? IdentifyPayload
+        XCTAssertEqual(identify?.userId, "testUserId1")
+        XCTAssertEqual(identify?.anonymousId, "a_custom_anonymous_id")
+        XCTAssertEqual(identify?.traits?["firstName"] as? String, "Peter")
     }
-
-    it("handles track:") {
-      analytics.track("User Signup", properties: [
-        "method": "SSO"
-      ], options: [
-        "context": [
-          "device": [
-            "token": "1234"
-          ]
-        ]
-      ])
-      expect(passthrough.lastContext?.eventType) == EventType.track
-      let payload = passthrough.lastContext?.payload as? TrackPayload
-      expect(payload?.event) == "User Signup"
-      expect(payload?.properties?["method"] as? String) == "SSO"
+    
+    func testHandlesTrack() {
+        analytics.track("User Signup", properties: [
+            "method": "SSO"
+            ], options: [
+                "context": [
+                    "device": [
+                        "token": "1234"
+                    ]
+                ]
+        ])
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.track)
+        let payload = passthrough.lastContext?.payload as? TrackPayload
+        XCTAssertEqual(payload?.event, "User Signup")
+        XCTAssertEqual(payload?.properties?["method"] as? String, "SSO")
     }
-
-    it("handles alias:") {
-      analytics.alias("persistentUserId")
-      expect(passthrough.lastContext?.eventType) == EventType.alias
-      let payload = passthrough.lastContext?.payload as? AliasPayload
-      expect(payload?.theNewId) == "persistentUserId"
+    
+    func testHandlesAlias() {
+        analytics.alias("persistentUserId")
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.alias)
+        let payload = passthrough.lastContext?.payload as? AliasPayload
+        XCTAssertEqual(payload?.theNewId, "persistentUserId")
     }
-
-    it("handles screen:") {
-      analytics.screen("Home", properties: [
-        "referrer": "Google"
-      ])
-      expect(passthrough.lastContext?.eventType) == EventType.screen
-      let screen = passthrough.lastContext?.payload as? ScreenPayload
-      expect(screen?.name) == "Home"
-      expect(screen?.properties?["referrer"] as? String) == "Google"
+    
+    func testHandlesScreen() {
+        analytics.screen("Home", properties: [
+            "referrer": "Google"
+        ])
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.screen)
+        let screen = passthrough.lastContext?.payload as? ScreenPayload
+        XCTAssertEqual(screen?.name, "Home")
+        XCTAssertEqual(screen?.properties?["referrer"] as? String, "Google")
     }
-
-    it("handles group:") {
-      analytics.group("acme-company", traits: [
-        "employees": 2333
-      ])
-      expect(passthrough.lastContext?.eventType) == EventType.group
-      let payload = passthrough.lastContext?.payload as? GroupPayload
-      expect(payload?.groupId) == "acme-company"
-      expect(payload?.traits?["employees"] as? Int) == 2333
+    
+    func testHandlesGroup() {
+        analytics.group("acme-company", traits: [
+            "employees": 2333
+        ])
+        XCTAssertEqual(passthrough.lastContext?.eventType, EventType.group)
+        let payload = passthrough.lastContext?.payload as? GroupPayload
+        XCTAssertEqual(payload?.groupId, "acme-company")
+        XCTAssertEqual(payload?.traits?["employees"] as? Int, 2333)
     }
     
-    it("handles null values") {
-      analytics.track("null test", properties: [
-        "nullTest": NSNull()
+    func testHandlesNullValues() {
+        analytics.track("null test", properties: [
+            "nullTest": NSNull()
         ])
-      let payload = passthrough.lastContext?.payload as? TrackPayload
-      let isNull = (payload?.properties?["nullTest"] is NSNull)
-      expect(isNull) == true
+        let payload = passthrough.lastContext?.payload as? TrackPayload
+        let isNull = (payload?.properties?["nullTest"] is NSNull)
+        XCTAssert(isNull)
     }
-  }
-
 }
diff --git a/AnalyticsTests/UserDefaultsStorageTest.swift b/AnalyticsTests/UserDefaultsStorageTest.swift
index 727225157..5e84830d3 100644
--- a/AnalyticsTests/UserDefaultsStorageTest.swift
+++ b/AnalyticsTests/UserDefaultsStorageTest.swift
@@ -5,99 +5,96 @@
 //  Copyright © 2016 Segment. All rights reserved.
 //
 
-import Quick
-import Nimble
-import Analytics
+@testable import Analytics
+import XCTest
 
-class UserDefaultsStorageTest : QuickSpec {
-  override func spec() {
+class UserDefaultsStorageTest : XCTestCase {
+    
     var storage : UserDefaultsStorage!
-    beforeEach {
-//      let crypto = SEGAES256Crypto(password: "thetrees")
-//      storage = SEGUserDefaultsStorage(defaults: NSUserDefaults.standardUserDefaults(), namespacePrefix: "segment", crypto: crypto)
-//      storage = SEGUserDefaultsStorage(defaults: NSUserDefaults.standardUserDefaults(), namespacePrefix: nil, crypto: crypto)
-      storage = UserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: nil, crypto: nil)
-    }
     
-    it("persists and loads data") {
-      let dataIn = "segment".data(using: String.Encoding.utf8)!
-      storage.setData(dataIn, forKey: "mydata")
-      
-      let dataOut = storage.data(forKey: "mydata")
-      expect(dataOut) == dataIn
-      
-      let strOut = String(data: dataOut!, encoding: .utf8)
-      expect(strOut) == "segment"
+    override func setUp() {
+        super.setUp()
+        storage = UserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: nil, crypto: nil)
     }
     
-    it("persists and loads string") {
-      let str = "san francisco"
-      storage.setString(str, forKey: "city")
-      expect(storage.string(forKey: "city")) == str
-      
-      storage.removeKey("city")
-      expect(storage.string(forKey: "city")).to(beNil())
+    override func tearDown() {
+        super.tearDown()
+        storage.resetAll()
     }
     
-    it("persists and loads array") {
-      let array = [
-        "san francisco",
-        "new york",
-        "tallinn",
-      ]
-      storage.setArray(array, forKey: "cities")
-      expect(storage.array(forKey: "cities") as? Array<String>) == array
-      
-      storage.removeKey("cities")
-      expect(storage.array(forKey: "cities")).to(beNil())
+    func testPersistsAndLoadsData() {
+        let dataIn = "segment".data(using: String.Encoding.utf8)!
+        storage.setData(dataIn, forKey: "mydata")
+        
+        let dataOut = storage.data(forKey: "mydata")
+        XCTAssertEqual(dataOut, dataIn)
+        
+        let strOut = String(data: dataOut!, encoding: .utf8)
+        XCTAssertEqual(strOut, "segment")
     }
     
-    it("persists and loads dictionary") {
-      let dict = [
-        "san francisco": "tech",
-        "new york": "finance",
-        "paris": "fashion",
-      ]
-      storage.setDictionary(dict, forKey: "cityMap")
-      expect(storage.dictionary(forKey: "cityMap") as? Dictionary<String, String>) == dict
-      
-      storage.removeKey("cityMap")
-      expect(storage.dictionary(forKey: "cityMap")).to(beNil())
+    func testPersistsAndLoadsString() {
+        let str = "san francisco"
+        storage.setString(str, forKey: "city")
+        XCTAssertEqual(storage.string(forKey: "city"), str)
+        
+        storage.removeKey("city")
+        XCTAssertNil(storage.string(forKey: "city"))
     }
     
-    it("should work with crypto") {
-      let crypto = AES256Crypto(password: "thetrees")
-      let s = UserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: nil, crypto: crypto)
-      let dict = [
-        "san francisco": "tech",
-        "new york": "finance",
-        "paris": "fashion",
-      ]
-      s.setDictionary(dict, forKey: "cityMap")
-      expect(s.dictionary(forKey: "cityMap") as? Dictionary<String, String>) == dict
-      
-      s.removeKey("cityMap")
-      expect(s.dictionary(forKey: "cityMap")).to(beNil())
+    func testPersistsAndLoadsArray() {
+        let array = [
+            "san francisco",
+            "new york",
+            "tallinn",
+        ]
+        storage.setArray(array, forKey: "cities")
+        XCTAssertEqual(storage.array(forKey: "cities") as? Array<String>, array)
+        
+        storage.removeKey("cities")
+        XCTAssertNil(storage.array(forKey: "cities"))
     }
     
+    func testPersistsAndLoadsDictionary() {
+        let dict = [
+            "san francisco": "tech",
+            "new york": "finance",
+            "paris": "fashion",
+        ]
+        storage.setDictionary(dict, forKey: "cityMap")
+        XCTAssertEqual(storage.dictionary(forKey: "cityMap") as? Dictionary<String, String>, dict)
+        
+        storage.removeKey("cityMap")
+        XCTAssertNil(storage.dictionary(forKey: "cityMap"))
+    }
     
-    it("should work with namespace") {
-      let crypto = AES256Crypto(password: "thetrees")
-      let s = UserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: "segment", crypto: crypto)
-      let dict = [
-        "san francisco": "tech",
-        "new york": "finance",
-        "paris": "fashion",
-      ]
-      s.setDictionary(dict, forKey: "cityMap")
-      expect(s.dictionary(forKey: "cityMap") as? Dictionary<String, String>) == dict
-      
-      s.removeKey("cityMap")
-      expect(s.dictionary(forKey: "cityMap")).to(beNil())
+    func testShouldWorkWithCrypto() {
+        let crypto = AES256Crypto(password: "thetrees")
+        let s = UserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: nil, crypto: crypto)
+        let dict = [
+            "san francisco": "tech",
+            "new york": "finance",
+            "paris": "fashion",
+        ]
+        s.setDictionary(dict, forKey: "cityMap")
+        XCTAssertEqual(s.dictionary(forKey: "cityMap") as? Dictionary<String, String>, dict)
+        
+        s.removeKey("cityMap")
+        XCTAssertNil(s.dictionary(forKey: "cityMap"))
     }
     
-    afterEach {
-      storage.resetAll()
+    func testShouldWorkWithNamespace() {
+        let crypto = AES256Crypto(password: "thetrees")
+        let s = UserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: "segment", crypto: crypto)
+        let dict = [
+            "san francisco": "tech",
+            "new york": "finance",
+            "paris": "fashion",
+        ]
+        s.setDictionary(dict, forKey: "cityMap")
+        XCTAssertEqual(s.dictionary(forKey: "cityMap") as? Dictionary<String, String>, dict)
+        
+        s.removeKey("cityMap")
+        XCTAssertNil(s.dictionary(forKey: "cityMap"))
     }
-  }
 }
diff --git a/AnalyticsTests/Utils/TestUtils.swift b/AnalyticsTests/Utils/TestUtils.swift
index f0e9aa19f..97975751c 100644
--- a/AnalyticsTests/Utils/TestUtils.swift
+++ b/AnalyticsTests/Utils/TestUtils.swift
@@ -6,9 +6,9 @@
 //  Copyright © 2016 Segment. All rights reserved.
 //
 
-@testable import Nimble
 import Nocilla
 import Analytics
+import XCTest
 
 class PassthroughMiddleware: Middleware {
   var lastContext: Context?
@@ -85,12 +85,9 @@ class JsonGzippedBody : LSMatcher, LSMatcheable {
     }
     
     func matchesJson(_ json: AnyObject) -> Bool {
-        let actualValue : () -> NSObject = {
-            return json as! NSObject
-        }
-        let failureMessage = FailureMessage()
-        let location = SourceLocation()
-        let matches = Nimble.equal(expectedJson).matches(actualValue, failureMessage: failureMessage, location: location)
+        let expectedDictionary = expectedJson as? [String: AnyHashable]
+        let jsonDictionary = json as? [String: AnyHashable]
+        let matches = expectedDictionary == jsonDictionary
 //        print("matches=\(matches) expected \(expectedJson) actual \(json)")
         return matches
     }
@@ -184,3 +181,21 @@ class TestApplication: NSObject, ApplicationProtocol {
     backgroundTasks[index].isEnded = true
   }
 }
+
+extension XCTestCase {
+    
+    func expectUntil(_ time: TimeInterval, expression: @escaping @autoclosure () throws -> Bool) {
+        
+        let expectation = self.expectation(description: "Expect Until")
+        DispatchQueue.global().async {
+            while (true) {
+                if try! expression() {
+                    expectation.fulfill()
+                    return
+                }
+                usleep(500) // try every half second
+            }
+        }
+        wait(for: [expectation], timeout: time)
+    }
+}
diff --git a/Podfile b/Podfile
index 69cd2a684..576f68d20 100644
--- a/Podfile
+++ b/Podfile
@@ -1,6 +1,4 @@
 def shared_testing_pods
-    pod 'Quick', '~> 1.2.0'
-    pod 'Nimble', '~> 7.3.4'
     pod 'Nocilla', '~> 0.11.0'
     pod 'SwiftTryCatch', :git => 'https://github.com/segmentio/SwiftTryCatch.git'
 end
diff --git a/Podfile.lock b/Podfile.lock
index d09d52a7a..74fdd16eb 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -1,20 +1,14 @@
 PODS:
-  - Nimble (7.3.4)
   - Nocilla (0.11.0)
-  - Quick (1.2.0)
   - SwiftTryCatch (1.0.0)
 
 DEPENDENCIES:
-  - Nimble (~> 7.3.4)
   - Nocilla (~> 0.11.0)
-  - Quick (~> 1.2.0)
   - SwiftTryCatch (from `https://github.com/segmentio/SwiftTryCatch.git`)
 
 SPEC REPOS:
   trunk:
-    - Nimble
     - Nocilla
-    - Quick
 
 EXTERNAL SOURCES:
   SwiftTryCatch:
@@ -26,11 +20,9 @@ CHECKOUT OPTIONS:
     :git: https://github.com/segmentio/SwiftTryCatch.git
 
 SPEC CHECKSUMS:
-  Nimble: 051e3d8912d40138fa5591c78594f95fb172af37
   Nocilla: 7af7a386071150cc8aa5da4da97d060f049dd61c
-  Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08
   SwiftTryCatch: 6ef1f543b5d287a4f5763c0de2ee9c8568985269
 
-PODFILE CHECKSUM: 80d573cfd1dc222ba558c64e15bef5eb9d22c782
+PODFILE CHECKSUM: 0317236deb6b78344dafed0871776d4342f80caa
 
-COCOAPODS: 1.9.1
+COCOAPODS: 1.9.3

From bdc96554a0a7f5af50ae5500cf767fa97a56f005 Mon Sep 17 00:00:00 2001
From: Cody Garvin <emig647@gmail.com>
Date: Wed, 24 Jun 2020 17:03:42 -0700
Subject: [PATCH 2/2] Issue 906: Updated package to supports all types of
 libraries

---
 Package.swift | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/Package.swift b/Package.swift
index 289c556a9..1b8a6962e 100644
--- a/Package.swift
+++ b/Package.swift
@@ -5,11 +5,13 @@ import PackageDescription
 
 let package = Package(
     name: "Analytics",
+    platforms: [
+        .iOS(.v12), .tvOS(.v12)
+    ],
     products: [
         // Products define the executables and libraries produced by a package, and make them visible to other packages.
         .library(
             name: "Analytics",
-            type: .dynamic,
             targets: ["Analytics"]),
     ],
     dependencies: [
@@ -27,8 +29,9 @@ let package = Package(
             sources: ["Classes", "Internal"],
             publicHeadersPath: "Classes",
             cSettings: [
-            .headerSearchPath("Internal"),
-            .headerSearchPath("Classes"),
-        ]),
+                .headerSearchPath("Internal"),
+                .headerSearchPath("Classes")
+            ]
+        ),
     ]
 )