├── .gitignore ├── NimbleTests ├── objc │ ├── NimbleTests-Bridging-Header.h │ ├── Nimble-OSXTests-Bridging-Header.h │ └── CompatibilityTest.m ├── Helpers │ ├── ObjectWithLazyProperty.swift │ └── utils.swift ├── Matchers │ ├── BeNilTest.swift │ ├── MatchTest.swift │ ├── beOneOfTest.swift │ ├── BeAnInstanceOfTest.swift │ ├── BeAKindOfTest.swift │ ├── BeGreaterThanTest.swift │ ├── BeLessThanTest.swift │ ├── BeLessThanOrEqualToTest.swift │ ├── BeCloseToTest.swift │ ├── BeGreaterThanOrEqualToTest.swift │ ├── EndWithTest.swift │ ├── BeIdenticalToTest.swift │ ├── BeginWithTest.swift │ ├── BeIdenticalToObjectTest.swift │ ├── BeEmptyTest.swift │ ├── RaisesExceptionTest.swift │ ├── ContainTest.swift │ ├── BeLogicalTest.swift │ └── EqualTest.swift ├── Info.plist ├── AsynchronousTest.swift └── SynchronousTests.swift ├── Nimble.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── Nimble.xccheckout └── xcshareddata │ └── xcschemes │ ├── Nimble-OSX.xcscheme │ └── Nimble-iOS.xcscheme ├── Nimble ├── Nimble.h ├── Utils │ ├── Functional.swift │ ├── SourceLocation.swift │ ├── Poll.swift │ └── Stringers.swift ├── Adapters │ ├── AdapterProtocols.swift │ ├── XCTestHandler.swift │ └── AssertionRecorder.swift ├── objc │ ├── NMBExceptionCapture.h │ ├── NMBExceptionCapture.m │ ├── DSL.m │ └── DSL.h ├── Matchers │ ├── BeOneOf.swift │ ├── BeNil.swift │ ├── BeAKindOf.swift │ ├── BeAnInstanceOf.swift │ ├── Match.swift │ ├── BeIdenticalTo.swift │ ├── BeLessThan.swift │ ├── BeGreaterThan.swift │ ├── BeLessThanOrEqual.swift │ ├── BeGreaterThanOrEqualTo.swift │ ├── Contain.swift │ ├── BeginWith.swift │ ├── BeEmpty.swift │ ├── BeCloseTo.swift │ ├── EndWith.swift │ ├── MatcherProtocols.swift │ ├── BeLogical.swift │ ├── RaisesException.swift │ └── Equal.swift ├── Info.plist ├── Wrappers │ ├── MatcherFunc.swift │ ├── FullMatcherWrapper.swift │ ├── AsyncMatcherWrapper.swift │ └── ObjCMatcher.swift ├── Expectation.swift ├── Expression.swift ├── FailureMessage.swift └── DSL.swift ├── .travis.yml ├── test ├── CONTRIBUTING.md ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata/ 3 | build/ 4 | .idea 5 | DerivedData/ 6 | -------------------------------------------------------------------------------- /NimbleTests/objc/NimbleTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /NimbleTests/objc/Nimble-OSXTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /NimbleTests/Helpers/ObjectWithLazyProperty.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class ObjectWithLazyProperty { 4 | init() {} 5 | lazy var value: String = "hello" 6 | lazy var anotherValue: String = { return "world" }() 7 | } 8 | -------------------------------------------------------------------------------- /Nimble.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Nimble/Nimble.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | FOUNDATION_EXPORT double NimbleVersionNumber; 6 | FOUNDATION_EXPORT const unsigned char NimbleVersionString[]; 7 | -------------------------------------------------------------------------------- /Nimble/Utils/Functional.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func _all(array: [T], fn: (T) -> Bool) -> Bool { 4 | for item in array { 5 | if !fn(item) { 6 | return false 7 | } 8 | } 9 | return true 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Nimble/Adapters/AdapterProtocols.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol AssertionHandler { 4 | func assert(assertion: Bool, message: String, location: SourceLocation) 5 | } 6 | 7 | var CurrentAssertionHandler: AssertionHandler = XCTestHandler() 8 | 9 | -------------------------------------------------------------------------------- /Nimble/objc/NMBExceptionCapture.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NMBExceptionCapture : NSObject 4 | 5 | - (id)initWithHandler:(void(^)(NSException *))handler finally:(void(^)())finally; 6 | - (void)tryBlock:(void(^)())unsafeBlock; 7 | 8 | @end -------------------------------------------------------------------------------- /Nimble/Adapters/XCTestHandler.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | class XCTestHandler : AssertionHandler { 5 | func assert(assertion: Bool, message: String, location: SourceLocation) { 6 | if !assertion { 7 | XCTFail(message, file: location.file, line: location.line) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | env: 4 | matrix: 5 | - NIMBLE_BUILD_SDK_VERSION=8.0 NIMBLE_RUNTIME_SDK_VERSION=8.0 TYPE=ios 6 | # Until travis-ci supports these, we can't run this. 7 | # - NIMBLE_BUILD_SDK_VERSION=8.1 NIMBLE_RUNTIME_SDK_VERSION=8.1 TYPE=ios 8 | # - NIMBLE_BUILD_SDK_VERSION=8.1 NIMBLE_RUNTIME_SDK_VERSION=8.1 TYPE=osx 9 | 10 | script: ./test $TYPE 11 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeOneOf.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Nimble 3 | 4 | public func beOneOf(allowedValues: [T]) -> MatcherFunc { 5 | return MatcherFunc { actualExpression, failureMessage in 6 | failureMessage.postfixMessage = "be one of: \(stringify(allowedValues))" 7 | if let actualValue = actualExpression.evaluate() { 8 | return contains(allowedValues, actualValue) 9 | } 10 | return false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Nimble/Utils/SourceLocation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | @objc public class SourceLocation : Printable { 5 | public let file: String 6 | public let line: UInt 7 | 8 | init() { 9 | file = "Unknown File" 10 | line = 0 11 | } 12 | 13 | init(file: String, line: UInt) { 14 | self.file = file 15 | self.line = line 16 | } 17 | 18 | public var description: String { 19 | return "\(file):\(line)" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Nimble/Utils/Poll.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func _pollBlock(#pollInterval: NSTimeInterval, #timeoutInterval: NSTimeInterval, expression: () -> Bool) -> Bool { 4 | let startDate = NSDate() 5 | var pass: Bool 6 | do { 7 | pass = expression() 8 | if pass { 9 | break 10 | } 11 | 12 | let runDate = NSDate().dateByAddingTimeInterval(pollInterval) as NSDate 13 | NSRunLoop.mainRunLoop().runUntilDate(runDate) 14 | } while(NSDate().timeIntervalSinceDate(startDate) < timeoutInterval); 15 | 16 | return pass 17 | } 18 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeNilTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeNilTest: XCTestCase { 5 | func producesNil() -> Array? { 6 | return nil 7 | } 8 | 9 | func testBeNil() { 10 | expect(nil as Int?).to(beNil()) 11 | expect(1 as Int?).toNot(beNil()) 12 | expect(producesNil()).to(beNil()) 13 | 14 | failsWithErrorMessage("expected to not be nil, got ") { 15 | expect(nil as Int?).toNot(beNil()) 16 | } 17 | 18 | failsWithErrorMessage("expected to be nil, got <1>") { 19 | expect(1 as Int?).to(beNil()) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeNil.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beNil() -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "be nil" 6 | let actualValue = actualExpression.evaluate() 7 | return actualValue == nil 8 | } 9 | } 10 | 11 | extension NMBObjCMatcher { 12 | public class func beNilMatcher() -> NMBObjCMatcher { 13 | return NMBObjCMatcher { actualBlock, failureMessage, location in 14 | let block = ({ actualBlock() as NSObject? }) 15 | let expr = Expression(expression: block, location: location) 16 | return beNil().matches(expr, failureMessage: failureMessage) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/MatchTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class MatchTest:XCTestCase { 5 | func testMatchPositive() { 6 | expect("11:14").to(match("\\d{2}:\\d{2}")) 7 | } 8 | 9 | func testMatchNegative() { 10 | expect("hello").toNot(match("\\d{2}:\\d{2}")) 11 | } 12 | 13 | func testMatchPositiveMessage() { 14 | let message = "expected to match <\\d{2}:\\d{2}>, got " 15 | failsWithErrorMessage(message) { 16 | expect("hello").to(match("\\d{2}:\\d{2}")) 17 | } 18 | } 19 | 20 | func testMatchNegativeMessage() { 21 | let message = "expected to not match <\\d{2}:\\d{2}>, got <11:14>" 22 | failsWithErrorMessage(message) { 23 | expect("11:14").toNot(match("\\d{2}:\\d{2}")) 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /NimbleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.jeffhui.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/beOneOfTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class beOneOfTest: XCTestCase { 5 | func testPositiveMatches() { 6 | expect(1).to(beOneOf([1, 2, 3])) 7 | expect(4).toNot(beOneOf([1, 2, 3])) 8 | 9 | expect(1 as CInt).to(beOneOf([1, 2, 3])) 10 | expect(4 as CInt).toNot(beOneOf([1, 2, 3])) 11 | 12 | expect(NSString(string: "a")).to(beOneOf(["a", "b", "c"])) 13 | expect(NSString(string: "d")).toNot(beOneOf(["a", "b", "c"])) 14 | } 15 | 16 | func testNegativeMatches() { 17 | failsWithErrorMessage("expected to not be one of: [1, 2, 3], got <1>") { 18 | expect(1).toNot(beOneOf([1, 2, 3])) 19 | } 20 | failsWithErrorMessage("expected to be one of: [1, 2, 3], got <4>") { 21 | expect(4).to(beOneOf([1, 2, 3])) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Nimble/objc/NMBExceptionCapture.m: -------------------------------------------------------------------------------- 1 | #import "NMBExceptionCapture.h" 2 | 3 | @interface NMBExceptionCapture () 4 | @property (nonatomic, copy) void(^handler)(NSException *exception); 5 | @property (nonatomic, copy) void(^finally)(); 6 | @end 7 | 8 | @implementation NMBExceptionCapture 9 | 10 | - (id)initWithHandler:(void(^)(NSException *))handler finally:(void(^)())finally { 11 | self = [super init]; 12 | if (self) { 13 | self.handler = handler; 14 | self.finally = finally; 15 | } 16 | return self; 17 | } 18 | 19 | - (void)tryBlock:(void(^)())unsafeBlock { 20 | @try { 21 | unsafeBlock(); 22 | } 23 | @catch (NSException *exception) { 24 | if (self.handler) { 25 | self.handler(exception); 26 | } 27 | } 28 | @finally { 29 | if (self.finally) { 30 | self.finally(); 31 | } 32 | } 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeAnInstanceOfTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeAnInstanceOfTest: XCTestCase { 5 | func testPositiveMatch() { 6 | expect(nil as NSNull?).toNot(beAnInstanceOf(NSNull)) 7 | 8 | expect(NSNull()).to(beAnInstanceOf(NSNull)) 9 | expect(NSNumber(integer:1)).toNot(beAnInstanceOf(NSDate)) 10 | } 11 | 12 | func testFailureMessages() { 13 | failsWithErrorMessage("expected to be an instance of NSString, got ") { 14 | expect(nil as NSString?).to(beAnInstanceOf(NSString)) 15 | } 16 | failsWithErrorMessage("expected to be an instance of NSString, got <__NSCFNumber instance>") { 17 | expect(NSNumber(integer:1)).to(beAnInstanceOf(NSString)) 18 | } 19 | failsWithErrorMessage("expected to not be an instance of NSNumber, got <__NSCFNumber instance>") { 20 | expect(NSNumber(integer:1)).toNot(beAnInstanceOf(NSNumber)) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeAKindOfTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class TestNull : NSNull {} 5 | 6 | class BeAKindOfTest: XCTestCase { 7 | func testPositiveMatch() { 8 | expect(nil as NSNull?).toNot(beAKindOf(NSNull)) 9 | 10 | expect(TestNull()).to(beAKindOf(NSNull)) 11 | expect(NSObject()).to(beAKindOf(NSObject)) 12 | expect(NSNumber(integer:1)).toNot(beAKindOf(NSDate)) 13 | } 14 | 15 | func testFailureMessages() { 16 | failsWithErrorMessage("expected to be a kind of NSString, got ") { 17 | expect(nil as NSString?).to(beAKindOf(NSString)) 18 | } 19 | failsWithErrorMessage("expected to be a kind of NSString, got <__NSCFNumber instance>") { 20 | expect(NSNumber(integer:1)).to(beAKindOf(NSString)) 21 | } 22 | failsWithErrorMessage("expected to not be a kind of NSNumber, got <__NSCFNumber instance>") { 23 | expect(NSNumber(integer:1)).toNot(beAKindOf(NSNumber)) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Nimble/Adapters/AssertionRecorder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct AssertionRecord { 4 | public let success: Bool 5 | public let message: String 6 | public let location: SourceLocation 7 | } 8 | 9 | public class AssertionRecorder : AssertionHandler { 10 | public var assertions = [AssertionRecord]() 11 | 12 | public init() {} 13 | 14 | public func assert(assertion: Bool, message: String, location: SourceLocation) { 15 | assertions.append( 16 | AssertionRecord( 17 | success: assertion, 18 | message: message, 19 | location: location)) 20 | } 21 | } 22 | 23 | public func withAssertionHandler(recorder: AssertionHandler, closure: () -> Void) { 24 | let oldRecorder = CurrentAssertionHandler 25 | let capturer = NMBExceptionCapture(handler: nil, finally: ({ 26 | CurrentAssertionHandler = oldRecorder 27 | })) 28 | CurrentAssertionHandler = recorder 29 | capturer.tryBlock { 30 | closure() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Nimble/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.jeffhui.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | NSHumanReadableCopyright 26 | Copyright © 2014 Jeff Hui. All rights reserved. 27 | 28 | 29 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeGreaterThanTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeGreaterThanTest: XCTestCase { 5 | func testGreaterThan() { 6 | expect(10).to(beGreaterThan(2)) 7 | expect(1).toNot(beGreaterThan(2)) 8 | expect(NSNumber(int:3)).to(beGreaterThan(2)) 9 | expect(NSNumber(int:1)).toNot(beGreaterThan(NSNumber(int:2))) 10 | 11 | failsWithErrorMessage("expected to be greater than <2>, got <0>") { 12 | expect(0).to(beGreaterThan(2)) 13 | return 14 | } 15 | failsWithErrorMessage("expected to not be greater than <0>, got <1>") { 16 | expect(1).toNot(beGreaterThan(0)) 17 | return 18 | } 19 | } 20 | 21 | func testGreaterThanOperator() { 22 | expect(1) > 0 23 | expect(NSNumber(int:1)) > NSNumber(int:0) 24 | expect(NSNumber(int:1)) > 0 25 | 26 | failsWithErrorMessage("expected to be greater than <2.0000>, got <1.0000>") { 27 | expect(1) > 2 28 | return 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeAKindOf.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beAKindOf(expectedClass: AnyClass) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | let instance = actualExpression.evaluate() 6 | if let validInstance = instance { 7 | failureMessage.actualValue = "<\(NSStringFromClass(validInstance.dynamicType)) instance>" 8 | } else { 9 | failureMessage.actualValue = "" 10 | } 11 | failureMessage.postfixMessage = "be a kind of \(NSStringFromClass(expectedClass))" 12 | return instance != nil && instance!.isKindOfClass(expectedClass) 13 | } 14 | } 15 | 16 | extension NMBObjCMatcher { 17 | public class func beAKindOfMatcher(expected: AnyClass) -> NMBMatcher { 18 | return NMBObjCMatcher { actualExpression, failureMessage, location in 19 | let expr = Expression(expression: actualExpression, location: location) 20 | return beAKindOf(expected).matches(expr, failureMessage: failureMessage) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /NimbleTests/Helpers/utils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Nimble 3 | import XCTest 4 | 5 | func failsWithErrorMessage(message: String, closure: () -> Void, file: String = __FILE__, line: Int = __LINE__) { 6 | let recorder = AssertionRecorder() 7 | withAssertionHandler(recorder, closure) 8 | 9 | var lastFailureMessage: String? 10 | if recorder.assertions.count > 0 { 11 | lastFailureMessage = recorder.assertions[recorder.assertions.endIndex - 1].message 12 | if lastFailureMessage == message { 13 | return 14 | } 15 | } 16 | if lastFailureMessage != nil { 17 | let msg = "Got failure message: \"\(lastFailureMessage!)\", but expected \"\(message)\"" 18 | XCTFail(msg, file: file, line: UInt(line)) 19 | } else { 20 | XCTFail("expected failure message, but got none", file: file, line: UInt(line)) 21 | } 22 | } 23 | 24 | func deferToMainQueue(action: () -> Void) { 25 | dispatch_async(dispatch_get_main_queue()) { 26 | NSThread.sleepForTimeInterval(0.01) 27 | action() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeAnInstanceOf.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beAnInstanceOf(expectedClass: AnyClass) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | let instance = actualExpression.evaluate() 6 | if let validInstance = instance { 7 | failureMessage.actualValue = "<\(NSStringFromClass(validInstance.dynamicType)) instance>" 8 | } else { 9 | failureMessage.actualValue = "" 10 | } 11 | failureMessage.postfixMessage = "be an instance of \(NSStringFromClass(expectedClass))" 12 | return instance != nil && instance!.isMemberOfClass(expectedClass) 13 | } 14 | } 15 | 16 | extension NMBObjCMatcher { 17 | public class func beAnInstanceOfMatcher(expected: AnyClass) -> NMBMatcher { 18 | return NMBObjCMatcher { actualExpression, failureMessage, location in 19 | let expr = Expression(expression: actualExpression, location: location) 20 | return beAnInstanceOf(expected).matches(expr, failureMessage: failureMessage) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeLessThanTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeLessThanTest: XCTestCase { 5 | 6 | func testLessThan() { 7 | expect(2).to(beLessThan(10)) 8 | expect(2).toNot(beLessThan(1)) 9 | expect(NSNumber(integer:2)).to(beLessThan(10)) 10 | expect(NSNumber(integer:2)).toNot(beLessThan(1)) 11 | 12 | expect(2).to(beLessThan(NSNumber(integer:10))) 13 | expect(2).toNot(beLessThan(NSNumber(integer:1))) 14 | 15 | failsWithErrorMessage("expected to be less than <0>, got <2>") { 16 | expect(2).to(beLessThan(0)) 17 | return 18 | } 19 | failsWithErrorMessage("expected to not be less than <1>, got <0>") { 20 | expect(0).toNot(beLessThan(1)) 21 | return 22 | } 23 | } 24 | 25 | func testLessThanOperator() { 26 | expect(0) < 1 27 | expect(NSNumber(int:0)) < 1 28 | 29 | failsWithErrorMessage("expected to be less than <1.0000>, got <2.0000>") { 30 | expect(2) < 1 31 | return 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Nimble/Matchers/Match.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func match(expectedValue:String?) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "match <\(stringify(expectedValue))>" 6 | 7 | if let actual = actualExpression.evaluate() { 8 | if let regexp = expectedValue { 9 | return actual.rangeOfString(regexp, options: .RegularExpressionSearch) != nil 10 | } 11 | } 12 | 13 | return false 14 | } 15 | } 16 | 17 | extension NMBObjCMatcher { 18 | public class func matchMatcher(expected: NSString) -> NMBMatcher { 19 | return NMBObjCMatcher { actualBlock, failureMessage, location in 20 | let actual = actualBlock() 21 | if let actualString = actual as? String { 22 | let expr = Expression(expression: ({ actualString }), location: location) 23 | return match(expected).matches(expr, failureMessage: failureMessage) 24 | } 25 | return false 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Nimble/Wrappers/MatcherFunc.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct MatcherFunc: BasicMatcher { 4 | public let matcher: (Expression, FailureMessage) -> Bool 5 | 6 | public init(_ matcher: (Expression, FailureMessage) -> Bool) { 7 | self.matcher = matcher 8 | } 9 | 10 | public func matches(actualExpression: Expression, failureMessage: FailureMessage) -> Bool { 11 | return matcher(actualExpression, failureMessage) 12 | } 13 | 14 | public func withFailureMessage(postprocessor: (FailureMessage) -> Void) -> MatcherFunc { 15 | return MatcherFunc { actualExpression, failureMessage in 16 | let result = self.matches(actualExpression, failureMessage: failureMessage) 17 | postprocessor(failureMessage) 18 | return result 19 | } 20 | } 21 | } 22 | 23 | func _objc(matcher: MatcherFunc) -> NMBObjCMatcher { 24 | return NMBObjCMatcher { actualExpression, failureMessage, location in 25 | let expr = Expression(expression: actualExpression, location: location) 26 | return matcher.matches(expr, failureMessage: failureMessage) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeLessThanOrEqualToTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeLessThanOrEqualToTest: XCTestCase { 5 | func testLessThanOrEqualTo() { 6 | expect(10).to(beLessThanOrEqualTo(10)) 7 | expect(2).to(beLessThanOrEqualTo(10)) 8 | expect(2).toNot(beLessThanOrEqualTo(1)) 9 | 10 | expect(NSNumber(int:2)).to(beLessThanOrEqualTo(10)) 11 | expect(NSNumber(int:2)).toNot(beLessThanOrEqualTo(1)) 12 | expect(2).to(beLessThanOrEqualTo(NSNumber(int:10))) 13 | expect(2).toNot(beLessThanOrEqualTo(NSNumber(int:1))) 14 | 15 | failsWithErrorMessage("expected to be less than or equal to <0>, got <2>") { 16 | expect(2).to(beLessThanOrEqualTo(0)) 17 | return 18 | } 19 | failsWithErrorMessage("expected to not be less than or equal to <0>, got <0>") { 20 | expect(0).toNot(beLessThanOrEqualTo(0)) 21 | return 22 | } 23 | } 24 | 25 | func testLessThanOrEqualToOperator() { 26 | expect(0) <= 1 27 | expect(1) <= 1 28 | 29 | failsWithErrorMessage("expected to be less than or equal to <1>, got <2>") { 30 | expect(2) <= 1 31 | return 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Nimble/Wrappers/FullMatcherWrapper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | struct FullMatcherWrapper: Matcher { 5 | let matcher: M 6 | let to: String 7 | let toNot: String 8 | 9 | func matches(actualExpression: Expression, failureMessage: FailureMessage) -> Bool { 10 | failureMessage.to = to 11 | let pass = matcher.matches(actualExpression, failureMessage: failureMessage) 12 | return pass 13 | } 14 | 15 | func doesNotMatch(actualExpression: Expression, failureMessage: FailureMessage) -> Bool { 16 | failureMessage.to = toNot 17 | let pass = matcher.matches(actualExpression, failureMessage: failureMessage) 18 | return !pass 19 | } 20 | } 21 | 22 | extension Expectation { 23 | public func to(matcher: U) { 24 | to(FullMatcherWrapper(matcher: matcher, to: "to", toNot: "to not")) 25 | } 26 | 27 | public func toNot(matcher: U) { 28 | toNot(FullMatcherWrapper(matcher: matcher, to: "to", toNot: "to not")) 29 | } 30 | 31 | public func notTo(matcher: U) { 32 | toNot(matcher) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeCloseToTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeCloseToTest: XCTestCase { 5 | func testBeCloseTo() { 6 | expect(1.2).to(beCloseTo(1.2001)) 7 | expect(1.2 as CDouble).to(beCloseTo(1.2001)) 8 | expect(1.2 as Float).to(beCloseTo(1.2001)) 9 | 10 | failsWithErrorMessage("expected to not be close to <1.2001> (within 0.0001), got <1.2000>") { 11 | expect(1.2).toNot(beCloseTo(1.2001)) 12 | } 13 | } 14 | 15 | func testBeCloseToWithin() { 16 | expect(1.2).to(beCloseTo(9.300, within: 10)) 17 | 18 | failsWithErrorMessage("expected to not be close to <1.2001> (within 1.0000), got <1.2000>") { 19 | expect(1.2).toNot(beCloseTo(1.2001, within: 1.0)) 20 | } 21 | } 22 | 23 | func testBeCloseToWithNSNumber() { 24 | expect(NSNumber(double:1.2)).to(beCloseTo(9.300, within: 10)) 25 | expect(NSNumber(double:1.2)).to(beCloseTo(NSNumber(double:9.300), within: 10)) 26 | expect(1.2).to(beCloseTo(NSNumber(double:9.300), within: 10)) 27 | 28 | failsWithErrorMessage("expected to not be close to <1.2001> (within 1.0000), got <1.2000>") { 29 | expect(NSNumber(double:1.2)).toNot(beCloseTo(1.2001, within: 1.0)) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Nimble/Expectation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Expectation { 4 | let expression: Expression 5 | 6 | public func verify(pass: Bool, _ message: String) { 7 | CurrentAssertionHandler.assert(pass, message: message, location: expression.location) 8 | } 9 | 10 | public func to(matcher: U) { 11 | var msg = FailureMessage() 12 | let pass = matcher.matches(expression, failureMessage: msg) 13 | if msg.actualValue == "" { 14 | msg.actualValue = "<\(stringify(expression.evaluate()))>" 15 | } 16 | verify(pass, msg.stringValue()) 17 | } 18 | 19 | public func toNot(matcher: U) { 20 | var msg = FailureMessage() 21 | let pass = matcher.doesNotMatch(expression, failureMessage: msg) 22 | if msg.actualValue == "" { 23 | msg.actualValue = "<\(stringify(expression.evaluate()))>" 24 | } 25 | verify(pass, msg.stringValue()) 26 | } 27 | 28 | public func notTo(matcher: U) { 29 | toNot(matcher) 30 | } 31 | 32 | // see FullMatcherWrapper and AsyncMatcherWrapper for extensions 33 | // see NMBExpectation for Objective-C interface 34 | } 35 | 36 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeGreaterThanOrEqualToTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeGreaterThanOrEqualToTest: XCTestCase { 5 | 6 | func testGreaterThanOrEqualTo() { 7 | expect(10).to(beGreaterThanOrEqualTo(10)) 8 | expect(10).to(beGreaterThanOrEqualTo(2)) 9 | expect(1).toNot(beGreaterThanOrEqualTo(2)) 10 | expect(NSNumber(int:1)).toNot(beGreaterThanOrEqualTo(2)) 11 | expect(NSNumber(int:2)).to(beGreaterThanOrEqualTo(NSNumber(int:2))) 12 | expect(1).to(beGreaterThanOrEqualTo(NSNumber(int:0))) 13 | 14 | failsWithErrorMessage("expected to be greater than or equal to <2>, got <0>") { 15 | expect(0).to(beGreaterThanOrEqualTo(2)) 16 | return 17 | } 18 | failsWithErrorMessage("expected to not be greater than or equal to <1>, got <1>") { 19 | expect(1).toNot(beGreaterThanOrEqualTo(1)) 20 | return 21 | } 22 | } 23 | 24 | func testGreaterThanOrEqualToOperator() { 25 | expect(0) >= 0 26 | expect(1) >= 0 27 | expect(NSNumber(int:1)) >= 1 28 | expect(NSNumber(int:1)) >= NSNumber(int:1) 29 | 30 | failsWithErrorMessage("expected to be greater than or equal to <2>, got <1>") { 31 | expect(1) >= 2 32 | return 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/EndWithTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class EndWithTest: XCTestCase { 5 | 6 | func testEndWithPositives() { 7 | expect([1, 2, 3]).to(endWith(3)) 8 | expect([1, 2, 3]).toNot(endWith(2)) 9 | 10 | expect("foobar").to(endWith("bar")) 11 | expect("foobar").toNot(endWith("oo")) 12 | 13 | expect(NSString(string: "foobar")).to(endWith("bar")) 14 | expect(NSString(string: "foobar")).toNot(endWith("oo")) 15 | 16 | expect(NSArray(array: ["a", "b"])).to(endWith("b")) 17 | expect(NSArray(array: ["a", "b"])).toNot(endWith("a")) 18 | expect(nil as NSArray?).toNot(endWith("a")) 19 | } 20 | 21 | func testEndWithNegatives() { 22 | failsWithErrorMessage("expected to end with <2>, got <[1, 2, 3]>") { 23 | expect([1, 2, 3]).to(endWith(2)) 24 | } 25 | failsWithErrorMessage("expected to not end with <3>, got <[1, 2, 3]>") { 26 | expect([1, 2, 3]).toNot(endWith(3)) 27 | } 28 | failsWithErrorMessage("expected to end with , got ") { 29 | expect("batman").to(endWith("atm")) 30 | } 31 | failsWithErrorMessage("expected to not end with , got ") { 32 | expect("batman").toNot(endWith("man")) 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeIdenticalToTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeIdenticalToTest: XCTestCase { 5 | func testBeIdenticalToPositive() { 6 | expect(NSNumber(integer:1)).to(beIdenticalTo(NSNumber(integer:1))) 7 | } 8 | 9 | func testBeIdenticalToNegative() { 10 | expect(NSNumber(integer:1)).toNot(beIdenticalTo("yo")) 11 | expect([1]).toNot(beIdenticalTo([1])) 12 | } 13 | 14 | func testBeIdenticalToPositiveMessage() { 15 | let num1 = NSNumber(integer:1) 16 | let num2 = NSNumber(integer:2) 17 | let message = NSString(format: "expected to be identical to <%p>, got <%p>", num2, num1) 18 | failsWithErrorMessage(message) { 19 | expect(num1).to(beIdenticalTo(num2)) 20 | } 21 | } 22 | 23 | func testBeIdenticalToNegativeMessage() { 24 | let value1 = NSArray(array: []) 25 | let value2 = NSArray(array: []) 26 | let message = NSString(format: "expected to not be identical to <%p>, got <%p>", value2, value1) 27 | failsWithErrorMessage(message) { 28 | expect(value1).toNot(beIdenticalTo(value2)) 29 | } 30 | } 31 | 32 | func testOperators() { 33 | expect(NSNumber(integer:1)) === NSNumber(integer:1) 34 | expect(NSNumber(integer:1)) !== NSNumber(integer:2) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeIdenticalTo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | public func beIdenticalTo(expected: T?) -> MatcherFunc { 5 | return MatcherFunc { actualExpression, failureMessage in 6 | let actual = actualExpression.evaluate() 7 | failureMessage.actualValue = "\(_identityAsString(actual))" 8 | failureMessage.postfixMessage = "be identical to \(_identityAsString(expected))" 9 | let matches = actual === expected && actual !== nil 10 | if !matches && actual === nil { 11 | failureMessage.postfixMessage += " (will not compare nils, use beNil() instead)" 12 | } 13 | return matches 14 | } 15 | } 16 | 17 | public func ===(lhs: Expectation, rhs: T?) { 18 | lhs.to(beIdenticalTo(rhs)) 19 | } 20 | public func !==(lhs: Expectation, rhs: T?) { 21 | lhs.toNot(beIdenticalTo(rhs)) 22 | } 23 | 24 | extension NMBObjCMatcher { 25 | public class func beIdenticalToMatcher(expected: NSObject?) -> NMBObjCMatcher { 26 | return NMBObjCMatcher { actualBlock, failureMessage, location in 27 | let block = ({ actualBlock() as NSObject? }) 28 | let expr = Expression(expression: block, location: location) 29 | return beIdenticalTo(expected).matches(expr, failureMessage: failureMessage) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeginWithTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeginWithTest: XCTestCase { 5 | 6 | func testPositiveMatches() { 7 | expect([1, 2, 3]).to(beginWith(1)) 8 | expect([1, 2, 3]).toNot(beginWith(2)) 9 | 10 | expect("foobar").to(beginWith("foo")) 11 | expect("foobar").toNot(beginWith("oo")) 12 | 13 | expect(NSString(string: "foobar")).to(beginWith("foo")) 14 | expect(NSString(string: "foobar")).toNot(beginWith("oo")) 15 | 16 | expect(NSArray(array: ["a", "b"])).to(beginWith("a")) 17 | expect(NSArray(array: ["a", "b"])).toNot(beginWith("b")) 18 | expect(nil as NSArray?).toNot(beginWith("b")) 19 | } 20 | 21 | func testNegativeMatches() { 22 | failsWithErrorMessage("expected to begin with <2>, got <[1, 2, 3]>") { 23 | expect([1, 2, 3]).to(beginWith(2)) 24 | } 25 | failsWithErrorMessage("expected to not begin with <1>, got <[1, 2, 3]>") { 26 | expect([1, 2, 3]).toNot(beginWith(1)) 27 | } 28 | failsWithErrorMessage("expected to begin with , got ") { 29 | expect("batman").to(beginWith("atm")) 30 | } 31 | failsWithErrorMessage("expected to not begin with , got ") { 32 | expect("batman").toNot(beginWith("bat")) 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Nimble/Expression.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Memoizes the given closure, only calling the passed 4 | // closure once; even if repeat calls to the returned closure 5 | func _memoizedClosure(closure: () -> T) -> (Bool) -> T { 6 | var cache: T? 7 | return ({ withoutCaching in 8 | if (withoutCaching || cache == nil) { 9 | cache = closure() 10 | } 11 | return cache! 12 | }) 13 | } 14 | 15 | public struct Expression { 16 | public let _expression: (Bool) -> T? 17 | public let location: SourceLocation 18 | public let _withoutCaching: Bool 19 | public var cache: T? 20 | 21 | public init(expression: () -> T?, location: SourceLocation) { 22 | self._expression = _memoizedClosure(expression) 23 | self.location = location 24 | self._withoutCaching = false 25 | } 26 | 27 | public init(memoizedExpression: (Bool) -> T?, location: SourceLocation, withoutCaching: Bool) { 28 | self._expression = memoizedExpression 29 | self.location = location 30 | self._withoutCaching = withoutCaching 31 | } 32 | 33 | public func evaluate() -> T? { 34 | return self._expression(_withoutCaching) 35 | } 36 | 37 | public func withoutCaching() -> Expression { 38 | return Expression(memoizedExpression: self._expression, location: location, withoutCaching: true) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Nimble/FailureMessage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc 4 | public class FailureMessage { 5 | public var expected: String = "expected" 6 | public var actualValue: String? = "" // empty string -> use default; nil -> exclude 7 | public var to: String = "to" 8 | public var postfixMessage: String = "match" 9 | 10 | public init() { 11 | } 12 | 13 | var description : String { 14 | var value = "\(expected) \(to) \(postfixMessage)" 15 | if let actualValue = actualValue { 16 | value = "\(expected) \(actualValue) \(to) \(postfixMessage)" 17 | } 18 | var lines: [String] = (value as NSString).componentsSeparatedByString("\n") as [String] 19 | let whitespace = NSCharacterSet.whitespaceAndNewlineCharacterSet() 20 | lines = lines.map { line in line.stringByTrimmingCharactersInSet(whitespace) } 21 | return "".join(lines) 22 | } 23 | 24 | public func stringValue() -> String { 25 | var value = "\(expected) \(to) \(postfixMessage)" 26 | if let actualValue = actualValue { 27 | value = "\(expected) \(to) \(postfixMessage), got \(actualValue)" 28 | } 29 | var lines: [String] = (value as NSString).componentsSeparatedByString("\n") as [String] 30 | let whitespace = NSCharacterSet.whitespaceAndNewlineCharacterSet() 31 | lines = lines.map { line in line.stringByTrimmingCharactersInSet(whitespace) } 32 | return "".join(lines) 33 | } 34 | } -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeIdenticalToObjectTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeIdenticalToObjectTest:XCTestCase { 5 | private class BeIdenticalToObjectTester {} 6 | private let testObjectA = BeIdenticalToObjectTester() 7 | private let testObjectB = BeIdenticalToObjectTester() 8 | 9 | func testBeIdenticalToPositive() { 10 | expect(testObjectA).to(beIdenticalTo(testObjectA)) 11 | } 12 | 13 | func testBeIdenticalToNegative() { 14 | expect(testObjectA).toNot(beIdenticalTo(testObjectB)) 15 | } 16 | 17 | func testBeIdenticalToPositiveMessage() { 18 | let message = NSString(format: "expected to be identical to <%p>, got <%p>", 19 | unsafeBitCast(testObjectB, Int.self), unsafeBitCast(testObjectA, Int.self)) 20 | failsWithErrorMessage(message) { 21 | expect(self.testObjectA).to(beIdenticalTo(self.testObjectB)) 22 | } 23 | } 24 | 25 | func testBeIdenticalToNegativeMessage() { 26 | let message = NSString(format: "expected to not be identical to <%p>, got <%p>", 27 | unsafeBitCast(testObjectA, Int.self), unsafeBitCast(testObjectA, Int.self)) 28 | failsWithErrorMessage(message) { 29 | expect(self.testObjectA).toNot(beIdenticalTo(self.testObjectA)) 30 | } 31 | } 32 | 33 | func testOperators() { 34 | expect(testObjectA) === testObjectA 35 | expect(testObjectA) !== testObjectB 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeLessThan.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beLessThan(expectedValue: T?) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "be less than <\(stringify(expectedValue))>" 6 | return actualExpression.evaluate() < expectedValue 7 | } 8 | } 9 | 10 | public func beLessThan(expectedValue: NMBComparable?) -> MatcherFunc { 11 | return MatcherFunc { actualExpression, failureMessage in 12 | failureMessage.postfixMessage = "be less than <\(stringify(expectedValue))>" 13 | let actualValue = actualExpression.evaluate() 14 | let matches = actualValue != nil && actualValue!.NMB_compare(expectedValue) == NSComparisonResult.OrderedAscending 15 | return matches 16 | } 17 | } 18 | 19 | public func <(lhs: Expectation, rhs: T) { 20 | lhs.to(beLessThan(rhs)) 21 | } 22 | 23 | public func <(lhs: Expectation, rhs: NMBComparable?) { 24 | lhs.to(beLessThan(rhs)) 25 | } 26 | 27 | extension NMBObjCMatcher { 28 | public class func beLessThanMatcher(expected: NMBComparable?) -> NMBObjCMatcher { 29 | return NMBObjCMatcher { actualBlock, failureMessage, location in 30 | let block = ({ actualBlock() as NMBComparable? }) 31 | let expr = Expression(expression: block, location: location) 32 | return beLessThan(expected).matches(expr, failureMessage: failureMessage) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Nimble/Utils/Stringers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | func _identityAsString(value: AnyObject?) -> String { 5 | if value == nil { 6 | return "nil" 7 | } 8 | return NSString(format: "<%p>", unsafeBitCast(value!, Int.self)) 9 | } 10 | 11 | func _arrayAsString(items: [T], joiner: String = ", ") -> String { 12 | return items.reduce("") { accum, item in 13 | let prefix = (accum.isEmpty ? "" : joiner) 14 | return accum + prefix + "\(item)" 15 | } 16 | } 17 | 18 | @objc protocol NMBStringer { 19 | func NMB_stringify() -> String 20 | } 21 | 22 | func stringify(value: S) -> String { 23 | var generator = value.generate() 24 | var strings = [String]() 25 | var value: S.Generator.Element? 26 | do { 27 | value = generator.next() 28 | if value != nil { 29 | strings.append(stringify(value)) 30 | } 31 | } while value != nil 32 | let str = ", ".join(strings) 33 | return "[\(str)]" 34 | } 35 | 36 | extension NSArray : NMBStringer { 37 | func NMB_stringify() -> String { 38 | let str = self.componentsJoinedByString(", ") 39 | return "[\(str)]" 40 | } 41 | } 42 | 43 | func stringify(value: T) -> String { 44 | if value is Double { 45 | return NSString(format: "%.4f", (value as Double)) 46 | } 47 | return toString(value) 48 | } 49 | 50 | func stringify(value: T?) -> String { 51 | if let unboxed = value { 52 | return stringify(unboxed) 53 | } 54 | return "nil" 55 | } 56 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeGreaterThan.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beGreaterThan(expectedValue: T?) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "be greater than <\(stringify(expectedValue))>" 6 | return actualExpression.evaluate() > expectedValue 7 | } 8 | } 9 | 10 | public func beGreaterThan(expectedValue: NMBComparable?) -> MatcherFunc { 11 | return MatcherFunc { actualExpression, failureMessage in 12 | failureMessage.postfixMessage = "be greater than <\(stringify(expectedValue))>" 13 | let actualValue = actualExpression.evaluate() 14 | let matches = actualValue != nil && actualValue!.NMB_compare(expectedValue) == NSComparisonResult.OrderedDescending 15 | return matches 16 | } 17 | } 18 | 19 | public func >(lhs: Expectation, rhs: T) { 20 | lhs.to(beGreaterThan(rhs)) 21 | } 22 | 23 | public func >(lhs: Expectation, rhs: NMBComparable?) { 24 | lhs.to(beGreaterThan(rhs)) 25 | } 26 | 27 | extension NMBObjCMatcher { 28 | public class func beGreaterThanMatcher(expected: NMBComparable?) -> NMBObjCMatcher { 29 | return NMBObjCMatcher { actualBlock, failureMessage, location in 30 | let block = ({ actualBlock() as NMBComparable? }) 31 | let expr = Expression(expression: block, location: location) 32 | return beGreaterThan(expected).matches(expr, failureMessage: failureMessage) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeLessThanOrEqual.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beLessThanOrEqualTo(expectedValue: T?) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "be less than or equal to <\(stringify(expectedValue))>" 6 | return actualExpression.evaluate() <= expectedValue 7 | } 8 | } 9 | 10 | public func beLessThanOrEqualTo(expectedValue: T?) -> MatcherFunc { 11 | return MatcherFunc { actualExpression, failureMessage in 12 | failureMessage.postfixMessage = "be less than or equal to <\(stringify(expectedValue))>" 13 | let actualValue = actualExpression.evaluate() 14 | return actualValue != nil && actualValue!.NMB_compare(expectedValue) != NSComparisonResult.OrderedDescending 15 | } 16 | } 17 | 18 | public func <=(lhs: Expectation, rhs: T) { 19 | lhs.to(beLessThanOrEqualTo(rhs)) 20 | } 21 | 22 | public func <=(lhs: Expectation, rhs: T) { 23 | lhs.to(beLessThanOrEqualTo(rhs)) 24 | } 25 | 26 | extension NMBObjCMatcher { 27 | public class func beLessThanOrEqualToMatcher(expected: NMBComparable?) -> NMBObjCMatcher { 28 | return NMBObjCMatcher { actualBlock, failureMessage, location in 29 | let block = ({ actualBlock() as NMBComparable? }) 30 | let expr = Expression(expression: block, location: location) 31 | return beLessThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /NimbleTests/AsynchronousTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class AsyncTest: XCTestCase { 5 | 6 | func testAsyncPolling() { 7 | var value = 0 8 | deferToMainQueue { value = 1 } 9 | expect(value).toEventually(equal(1)) 10 | 11 | deferToMainQueue { value = 0 } 12 | expect(value).toEventuallyNot(equal(1)) 13 | 14 | failsWithErrorMessage("expected to eventually not equal <0>, got <0>") { 15 | expect(value).toEventuallyNot(equal(0)) 16 | } 17 | failsWithErrorMessage("expected to eventually equal <1>, got <0>") { 18 | expect(value).toEventually(equal(1)) 19 | } 20 | } 21 | 22 | func testAsyncCallback() { 23 | waitUntil { done in 24 | done() 25 | } 26 | waitUntil { done in 27 | deferToMainQueue { 28 | done() 29 | } 30 | } 31 | failsWithErrorMessage("Waited more than 1.0 second") { 32 | waitUntil(timeout: 1) { done in return } 33 | } 34 | failsWithErrorMessage("Waited more than 0.01 seconds") { 35 | waitUntil(timeout: 0.01) { done in 36 | NSThread.sleepForTimeInterval(0.1) 37 | done() 38 | } 39 | } 40 | 41 | failsWithErrorMessage("expected to equal <2>, got <1>") { 42 | waitUntil { done in 43 | NSThread.sleepForTimeInterval(0.1) 44 | expect(1).to(equal(2)) 45 | done() 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Nimble.xcodeproj/project.xcworkspace/xcshareddata/Nimble.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 3C7A95C5-450E-4971-8DC4-2613B2BA4376 9 | IDESourceControlProjectName 10 | Nimble 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 95438028B10BBB846574013D29F154A00556A9D1 14 | github.com:quick/Nimble.git 15 | 16 | IDESourceControlProjectPath 17 | Nimble.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 95438028B10BBB846574013D29F154A00556A9D1 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:quick/Nimble.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 95438028B10BBB846574013D29F154A00556A9D1 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 95438028B10BBB846574013D29F154A00556A9D1 36 | IDESourceControlWCCName 37 | Nimble 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeGreaterThanOrEqualTo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beGreaterThanOrEqualTo(expectedValue: T?) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "be greater than or equal to <\(stringify(expectedValue))>" 6 | let actualValue = actualExpression.evaluate() 7 | return actualValue >= expectedValue 8 | } 9 | } 10 | 11 | public func beGreaterThanOrEqualTo(expectedValue: T?) -> MatcherFunc { 12 | return MatcherFunc { actualExpression, failureMessage in 13 | failureMessage.postfixMessage = "be greater than or equal to <\(stringify(expectedValue))>" 14 | let actualValue = actualExpression.evaluate() 15 | let matches = actualValue != nil && actualValue!.NMB_compare(expectedValue) != NSComparisonResult.OrderedAscending 16 | return matches 17 | } 18 | } 19 | 20 | public func >=(lhs: Expectation, rhs: T) { 21 | lhs.to(beGreaterThanOrEqualTo(rhs)) 22 | } 23 | 24 | public func >=(lhs: Expectation, rhs: T) { 25 | lhs.to(beGreaterThanOrEqualTo(rhs)) 26 | } 27 | 28 | extension NMBObjCMatcher { 29 | public class func beGreaterThanOrEqualToMatcher(expected: NMBComparable?) -> NMBObjCMatcher { 30 | return NMBObjCMatcher { actualBlock, failureMessage, location in 31 | let block = ({ actualBlock() as NMBComparable? }) 32 | let expr = Expression(expression: block, location: location) 33 | return beGreaterThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeEmptyTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class BeEmptyTest: XCTestCase { 5 | func testBeEmptyPositive() { 6 | expect(nil as NSString?).to(beEmpty()) 7 | expect(nil as [CInt]?).to(beEmpty()) 8 | 9 | expect([]).to(beEmpty()) 10 | expect([1]).toNot(beEmpty()) 11 | 12 | expect([] as [CInt]).to(beEmpty()) 13 | expect([1] as [CInt]).toNot(beEmpty()) 14 | 15 | expect(NSDictionary()).to(beEmpty()) 16 | expect(NSDictionary(object: 1, forKey: 1)).toNot(beEmpty()) 17 | 18 | expect(Dictionary()).to(beEmpty()) 19 | expect(["hi": 1]).toNot(beEmpty()) 20 | 21 | expect(NSArray()).to(beEmpty()) 22 | expect(NSArray(array: [1])).toNot(beEmpty()) 23 | 24 | expect(NSSet()).to(beEmpty()) 25 | expect(NSSet(array: [1])).toNot(beEmpty()) 26 | 27 | expect(NSString()).to(beEmpty()) 28 | expect(NSString(string: "hello")).toNot(beEmpty()) 29 | 30 | expect("").to(beEmpty()) 31 | expect("foo").toNot(beEmpty()) 32 | } 33 | 34 | func testBeEmptyNegative() { 35 | failsWithErrorMessage("expected to not be empty, got <()>") { 36 | expect([]).toNot(beEmpty()) 37 | } 38 | failsWithErrorMessage("expected to be empty, got <[1]>") { 39 | expect([1]).to(beEmpty()) 40 | } 41 | 42 | failsWithErrorMessage("expected to not be empty, got <>") { 43 | expect("").toNot(beEmpty()) 44 | } 45 | failsWithErrorMessage("expected to be empty, got ") { 46 | expect("foo").to(beEmpty()) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BUILD_DIR=`pwd`/build 4 | BUILD_SDK_VERSION=${NIMBLE_BUILD_SDK_VERSION:-8.1} 5 | RUNTIME_SDK_VERSION=${NIMBLE_RUNTIME_SDK_VERSION:-8.1} 6 | 7 | set -e 8 | 9 | function run { 10 | echo "\x1B[01;92m==>\x1B[0m $@" 11 | "$@" 12 | } 13 | 14 | function test_ios { 15 | run osascript -e 'tell app "iOS Simulator" to quit' 16 | run xcodebuild -project Nimble.xcodeproj -scheme "Nimble-iOS" -configuration "Debug" -sdk "iphonesimulator$BUILD_SDK_VERSION" -destination "name=iPad Air,OS=$RUNTIME_SDK_VERSION" -destination-timeout 5 build test 17 | 18 | run osascript -e 'tell app "iOS Simulator" to quit' 19 | run xcodebuild -project Nimble.xcodeproj -scheme "Nimble-iOS" -configuration "Debug" -sdk "iphonesimulator$BUILD_SDK_VERSION" -destination "name=iPhone 5s,OS=$RUNTIME_SDK_VERSION" -destination-timeout 5 build test 20 | } 21 | 22 | function test_osx { 23 | run xcodebuild -project Nimble.xcodeproj -scheme "Nimble-OSX" -configuration "Debug" -sdk "macosx" -destination-timeout 5 build test 24 | } 25 | 26 | function test() { 27 | test_ios 28 | test_osx 29 | } 30 | 31 | function clean { 32 | run rm -rf ~/Library/Developer/Xcode/DerivedData 33 | } 34 | 35 | function help { 36 | echo "Usage: $0 COMMANDS" 37 | echo 38 | echo "COMMANDS:" 39 | echo " clean - Cleans the derived data directory of Xcode. Assumes default location" 40 | echo " ios - Runs the tests as an iOS device" 41 | echo " osx - Runs the tests on Mac OS X 10.10 (Yosemite and newer only)" 42 | echo " help - Displays this help" 43 | echo 44 | exit 1 45 | } 46 | 47 | function main { 48 | for arg in $@ 49 | do 50 | case "$arg" in 51 | clean) clean ;; 52 | ios) test_ios ;; 53 | osx) test_osx ;; 54 | test) test ;; 55 | all) test ;; 56 | help) help ;; 57 | esac 58 | done 59 | 60 | if [ $# -eq 0 ]; then 61 | clean 62 | test 63 | fi 64 | } 65 | 66 | main $@ 67 | 68 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/RaisesExceptionTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class RaisesExceptionTest: XCTestCase { 5 | var exception = NSException(name: "laugh", reason: "Lulz", userInfo: nil) 6 | 7 | func testCapturingInAutoClosure() { 8 | expect(exception.raise()).to(raiseException(named: "laugh")) 9 | expect(exception.raise()).to(raiseException()) 10 | 11 | failsWithErrorMessage("expected to raise exception named ") { 12 | expect(self.exception.raise()).to(raiseException(named: "foo")) 13 | } 14 | } 15 | 16 | func testCapturingInExplicitClosure() { 17 | expect { 18 | self.exception.raise() 19 | }.to(raiseException(named: "laugh")) 20 | 21 | expect { 22 | self.exception.raise() 23 | }.to(raiseException()) 24 | } 25 | 26 | func testCapturingWithReason() { 27 | expect(exception.raise()).to(raiseException(named: "laugh", reason: "Lulz")) 28 | 29 | failsWithErrorMessage("expected to raise any exception") { 30 | expect(self.exception).to(raiseException()) 31 | } 32 | failsWithErrorMessage("expected to not raise any exception") { 33 | expect(self.exception.raise()).toNot(raiseException()) 34 | } 35 | failsWithErrorMessage("expected to raise exception named and reason ") { 36 | expect(self.exception).to(raiseException(named: "laugh", reason: "Lulz")) 37 | } 38 | 39 | failsWithErrorMessage("expected to raise exception named and reason ") { 40 | expect(self.exception.raise()).to(raiseException(named: "bar", reason: "Lulz")) 41 | } 42 | failsWithErrorMessage("expected to not raise exception named ") { 43 | expect(self.exception.raise()).toNot(raiseException(named: "laugh")) 44 | } 45 | failsWithErrorMessage("expected to not raise exception named and reason ") { 46 | expect(self.exception.raise()).toNot(raiseException(named: "laugh", reason: "Lulz")) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Nimble/Matchers/Contain.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func contain(items: T...) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "contain <\(_arrayAsString(items))>" 6 | if let actual = actualExpression.evaluate() { 7 | return _all(items) { 8 | return contains(actual, $0) 9 | } 10 | } 11 | return false 12 | } 13 | } 14 | 15 | public func contain(substrings: String...) -> MatcherFunc { 16 | return MatcherFunc { actualExpression, failureMessage in 17 | failureMessage.postfixMessage = "contain <\(_arrayAsString(substrings))>" 18 | if let actual = actualExpression.evaluate() { 19 | return _all(substrings) { 20 | let scanRange = Range(start: actual.startIndex, end: actual.endIndex) 21 | let range = actual.rangeOfString($0, options: nil, range: scanRange, locale: nil) 22 | return range != nil && !range!.isEmpty 23 | } 24 | } 25 | return false 26 | } 27 | } 28 | 29 | public func contain(items: AnyObject?...) -> MatcherFunc { 30 | return MatcherFunc { actualExpression, failureMessage in 31 | failureMessage.postfixMessage = "contain <\(_arrayAsString(items))>" 32 | let actual = actualExpression.evaluate() 33 | return _all(items) { item in 34 | return actual != nil && actual!.containsObject(item) 35 | } 36 | } 37 | } 38 | 39 | extension NMBObjCMatcher { 40 | public class func containMatcher(expected: NSObject?) -> NMBObjCMatcher { 41 | return NMBObjCMatcher { actualBlock, failureMessage, location in 42 | let block: () -> NMBContainer? = ({ 43 | if let value = actualBlock() as? NMBContainer { 44 | return value 45 | } 46 | return nil 47 | }) 48 | let expr = Expression(expression: block, location: location) 49 | return contain(expected).matches(expr, failureMessage: failureMessage) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeginWith.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beginWith(startingElement: T) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "begin with <\(startingElement)>" 6 | if let actualValue = actualExpression.evaluate() { 7 | var actualGenerator = actualValue.generate() 8 | return actualGenerator.next() == startingElement 9 | } 10 | return false 11 | } 12 | } 13 | 14 | public func beginWith(startingElement: AnyObject) -> MatcherFunc { 15 | return MatcherFunc { actualExpression, failureMessage in 16 | failureMessage.postfixMessage = "begin with <\(startingElement)>" 17 | let collection = actualExpression.evaluate() 18 | return collection != nil && collection!.indexOfObject(startingElement) == 0 19 | } 20 | } 21 | 22 | public func beginWith(startingSubstring: String) -> MatcherFunc { 23 | return MatcherFunc { actualExpression, failureMessage in 24 | failureMessage.postfixMessage = "begin with <\(startingSubstring)>" 25 | if let actual = actualExpression.evaluate() { 26 | let range = actual.rangeOfString(startingSubstring) 27 | return range != nil && range!.startIndex == actual.startIndex 28 | } 29 | return false 30 | } 31 | } 32 | 33 | extension NMBObjCMatcher { 34 | public class func beginWithMatcher(expected: AnyObject) -> NMBObjCMatcher { 35 | return NMBObjCMatcher { actualBlock, failureMessage, location in 36 | let actual = actualBlock() 37 | if let actualString = actual as? String { 38 | let expr = Expression(expression: ({ actualString }), location: location) 39 | return beginWith(expected as NSString).matches(expr, failureMessage: failureMessage) 40 | } else { 41 | let expr = Expression(expression: ({ actual as? NMBOrderedCollection }), location: location) 42 | return beginWith(expected).matches(expr, failureMessage: failureMessage) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeEmpty.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func beEmpty() -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "be empty" 6 | let actualSeq = actualExpression.evaluate() 7 | if actualSeq == nil { 8 | return true 9 | } 10 | var generator = actualSeq!.generate() 11 | return generator.next() == nil 12 | } 13 | } 14 | 15 | public func beEmpty() -> MatcherFunc { 16 | return MatcherFunc { actualExpression, failureMessage in 17 | failureMessage.postfixMessage = "be empty" 18 | let actualString = actualExpression.evaluate() 19 | return actualString == nil || actualString!.length == 0 20 | } 21 | } 22 | 23 | // Without specific overrides, beEmpty() is ambiguous for NSDictionary, NSArray, 24 | // etc, since they conform to SequenceType as well as NMBCollection. 25 | public func beEmpty() -> MatcherFunc { 26 | return MatcherFunc { actualExpression, failureMessage in 27 | failureMessage.postfixMessage = "be empty" 28 | let actualDictionary = actualExpression.evaluate() 29 | return actualDictionary == nil || actualDictionary!.count == 0 30 | } 31 | } 32 | 33 | public func beEmpty() -> MatcherFunc { 34 | return MatcherFunc { actualExpression, failureMessage in 35 | failureMessage.postfixMessage = "be empty" 36 | let actualArray = actualExpression.evaluate() 37 | return actualArray == nil || actualArray!.count == 0 38 | } 39 | } 40 | 41 | public func beEmpty() -> MatcherFunc { 42 | return MatcherFunc { actualExpression, failureMessage in 43 | failureMessage.postfixMessage = "be empty" 44 | let actual = actualExpression.evaluate() 45 | return actual == nil || actual!.count == 0 46 | } 47 | } 48 | 49 | extension NMBObjCMatcher { 50 | class func beEmptyMatcher() -> NMBObjCMatcher { 51 | return NMBObjCMatcher { actualBlock, failureMessage, location in 52 | let block = ({ actualBlock() as? NMBCollection }) 53 | let expr = Expression(expression: block, location: location) 54 | return beEmpty().matches(expr, failureMessage: failureMessage) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeCloseTo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func _isCloseTo(actualValue: Double?, expectedValue: Double, delta: Double, failureMessage: FailureMessage) -> Bool { 4 | failureMessage.postfixMessage = "be close to <\(stringify(expectedValue))> (within \(stringify(delta)))" 5 | if actualValue != nil { 6 | failureMessage.actualValue = "<\(stringify(actualValue!))>" 7 | } else { 8 | failureMessage.actualValue = "" 9 | } 10 | return actualValue != nil && abs(actualValue! - expectedValue) < delta 11 | } 12 | 13 | public func beCloseTo(expectedValue: Double, within delta: Double = 0.0001) -> MatcherFunc { 14 | return MatcherFunc { actualExpression, failureMessage in 15 | return _isCloseTo(actualExpression.evaluate(), expectedValue, delta, failureMessage) 16 | } 17 | } 18 | 19 | public func beCloseTo(expectedValue: NMBDoubleConvertible, within delta: Double = 0.0001) -> MatcherFunc { 20 | return MatcherFunc { actualExpression, failureMessage in 21 | return _isCloseTo(actualExpression.evaluate()?.doubleValue, expectedValue.doubleValue, delta, failureMessage) 22 | } 23 | } 24 | 25 | @objc public class NMBObjCBeCloseToMatcher : NMBMatcher { 26 | var _expected: NSNumber 27 | var _delta: CDouble 28 | init(expected: NSNumber, within: CDouble) { 29 | _expected = expected 30 | _delta = within 31 | } 32 | 33 | public func matches(actualExpression: () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { 34 | let actualBlock: () -> NMBDoubleConvertible? = ({ 35 | return actualExpression() as? NMBDoubleConvertible 36 | }) 37 | let expr = Expression(expression: actualBlock, location: location) 38 | return beCloseTo(self._expected, within: self._delta).matches(expr, failureMessage: failureMessage) 39 | } 40 | 41 | public var within: (CDouble) -> NMBObjCBeCloseToMatcher { 42 | return ({ delta in 43 | return NMBObjCBeCloseToMatcher(expected: self._expected, within: delta) 44 | }) 45 | } 46 | } 47 | 48 | extension NMBObjCMatcher { 49 | public class func beCloseToMatcher(expected: NSNumber, within: CDouble) -> NMBObjCBeCloseToMatcher { 50 | return NMBObjCBeCloseToMatcher(expected: expected, within: within) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Nimble/DSL.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Begins an assertion on a given value. 4 | // file: and line: can be omitted to default to the current line this function is called on. 5 | public func expect(expression: @autoclosure () -> T?, file: String = __FILE__, line: UInt = __LINE__) -> Expectation { 6 | return Expectation( 7 | expression: Expression( 8 | expression: expression, 9 | location: SourceLocation(file: file, line: line))) 10 | } 11 | 12 | // Begins an assertion on a given value. 13 | // file: and line: can be omitted to default to the current line this function is called on. 14 | public func expect(file: String = __FILE__, line: UInt = __LINE__, expression: () -> T?) -> Expectation { 15 | return Expectation( 16 | expression: Expression( 17 | expression: expression, 18 | location: SourceLocation(file: file, line: line))) 19 | } 20 | 21 | // Begins an assertion on a given value. 22 | // file: and line: can be omitted to default to the current line this function is called on. 23 | public func waitUntil(#timeout: NSTimeInterval, action: (() -> Void) -> Void, file: String = __FILE__, line: UInt = __LINE__) -> Void { 24 | var completed = false 25 | dispatch_async(dispatch_get_main_queue()) { 26 | action() { completed = true } 27 | } 28 | let passed = _pollBlock(pollInterval: 0.01, timeoutInterval: timeout) { 29 | return completed 30 | } 31 | if !passed { 32 | let pluralize = (timeout == 1 ? "" : "s") 33 | fail("Waited more than \(timeout) second\(pluralize)", file: file, line: line) 34 | } 35 | } 36 | 37 | // Begins an assertion on a given value. 38 | // file: and line: can be omitted to default to the current line this function is called on. 39 | public func waitUntil(action: (() -> Void) -> Void, file: String = __FILE__, line: UInt = __LINE__) -> Void { 40 | waitUntil(timeout: 1, action, file: file, line: line) 41 | } 42 | 43 | public func fail(message: String, #location: SourceLocation) { 44 | CurrentAssertionHandler.assert(false, message: message, location: location) 45 | } 46 | 47 | public func fail(message: String, file: String = __FILE__, line: UInt = __LINE__) { 48 | fail(message, location: SourceLocation(file: file, line: line)) 49 | } 50 | 51 | public func fail(file: String = __FILE__, line: UInt = __LINE__) { 52 | fail("fail() always fails") 53 | } -------------------------------------------------------------------------------- /NimbleTests/Matchers/ContainTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class ContainTest: XCTestCase { 5 | func testContain() { 6 | expect([1, 2, 3]).to(contain(1)) 7 | expect([1, 2, 3] as [CInt]).to(contain(1 as CInt)) 8 | expect([1, 2, 3] as Array).to(contain(1 as CInt)) 9 | expect(["foo", "bar", "baz"]).to(contain("baz")) 10 | expect([1, 2, 3]).toNot(contain(4)) 11 | expect(["foo", "bar", "baz"]).toNot(contain("ba")) 12 | expect(NSArray(array: ["a"])).to(contain("a")) 13 | expect(NSArray(array: ["a"])).toNot(contain("b")) 14 | expect(NSArray(object: 1) as NSArray?).to(contain(1)) 15 | expect(nil as NSArray?).toNot(contain(1)) 16 | 17 | failsWithErrorMessage("expected to contain , got <[a, b, c]>") { 18 | expect(["a", "b", "c"]).to(contain("bar")) 19 | } 20 | failsWithErrorMessage("expected to not contain , got <[a, b, c]>") { 21 | expect(["a", "b", "c"]).toNot(contain("b")) 22 | } 23 | } 24 | 25 | func testContainSubstring() { 26 | expect("foo").to(contain("o")) 27 | expect("foo").to(contain("oo")) 28 | expect("foo").toNot(contain("z")) 29 | expect("foo").toNot(contain("zz")) 30 | 31 | failsWithErrorMessage("expected to contain , got ") { 32 | expect("foo").to(contain("bar")) 33 | } 34 | failsWithErrorMessage("expected to not contain , got ") { 35 | expect("foo").toNot(contain("oo")) 36 | } 37 | } 38 | 39 | func testContainObjCSubstring() { 40 | let str = NSString(string: "foo") 41 | expect(str).to(contain(NSString(string: "o"))) 42 | expect(str).to(contain(NSString(string: "oo"))) 43 | expect(str).toNot(contain(NSString(string: "z"))) 44 | expect(str).toNot(contain(NSString(string: "zz"))) 45 | } 46 | 47 | func testVariadicArguments() { 48 | expect([1, 2, 3]).to(contain(1, 2)) 49 | expect([1, 2, 3]).toNot(contain(1, 4)) 50 | 51 | failsWithErrorMessage("expected to contain , got <[a, b, c]>") { 52 | expect(["a", "b", "c"]).to(contain("a", "bar")) 53 | } 54 | 55 | failsWithErrorMessage("expected to not contain , got <[a, b, c]>") { 56 | expect(["a", "b", "c"]).toNot(contain("bar", "b")) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Nimble/Matchers/EndWith.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func endWith(endingElement: T) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "end with <\(endingElement)>" 6 | 7 | if let actualValue = actualExpression.evaluate() { 8 | var actualGenerator = actualValue.generate() 9 | var lastItem: T? 10 | var item: T? 11 | do { 12 | lastItem = item 13 | item = actualGenerator.next() 14 | } while(item != nil) 15 | 16 | return lastItem == endingElement 17 | } 18 | return false 19 | } 20 | } 21 | 22 | public func endWith(endingElement: AnyObject) -> MatcherFunc { 23 | return MatcherFunc { actualExpression, failureMessage in 24 | failureMessage.postfixMessage = "end with <\(endingElement)>" 25 | let collection = actualExpression.evaluate() 26 | return collection != nil && collection!.indexOfObject(endingElement) == collection!.count - 1 27 | } 28 | } 29 | 30 | public func endWith(endingSubstring: String) -> MatcherFunc { 31 | return MatcherFunc { actualExpression, failureMessage in 32 | failureMessage.postfixMessage = "end with <\(endingSubstring)>" 33 | if let collection = actualExpression.evaluate() { 34 | let range = collection.rangeOfString(endingSubstring) 35 | return range != nil && range!.endIndex == collection.endIndex 36 | } 37 | return false 38 | } 39 | } 40 | 41 | extension NMBObjCMatcher { 42 | public class func endWithMatcher(expected: AnyObject) -> NMBObjCMatcher { 43 | return NMBObjCMatcher { actualBlock, failureMessage, location in 44 | let actual = actualBlock() 45 | if let actualString = actual as? String { 46 | let expr = Expression(expression: ({ actualString }), location: location) 47 | return endWith(expected as NSString).matches(expr, failureMessage: failureMessage) 48 | } else { 49 | let expr = Expression(expression: ({ actual as? NMBOrderedCollection }), location: location) 50 | return endWith(expected).matches(expr, failureMessage: failureMessage) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Nimble/Wrappers/AsyncMatcherWrapper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct AsyncMatcherWrapper: Matcher { 4 | let fullMatcher: U 5 | let timeoutInterval: NSTimeInterval = 1 6 | let pollInterval: NSTimeInterval = 0.01 7 | 8 | func matches(actualExpression: Expression, failureMessage: FailureMessage) -> Bool { 9 | let uncachedExpression = actualExpression.withoutCaching() 10 | return _pollBlock(pollInterval: pollInterval, timeoutInterval: timeoutInterval) { 11 | self.fullMatcher.matches(uncachedExpression, failureMessage: failureMessage) 12 | } 13 | } 14 | 15 | func doesNotMatch(actualExpression: Expression, failureMessage: FailureMessage) -> Bool { 16 | let uncachedExpression = actualExpression.withoutCaching() 17 | return _pollBlock(pollInterval: pollInterval, timeoutInterval: timeoutInterval) { 18 | self.fullMatcher.doesNotMatch(uncachedExpression, failureMessage: failureMessage) 19 | } 20 | } 21 | } 22 | 23 | extension Expectation { 24 | public func toEventually(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01) { 25 | to(AsyncMatcherWrapper( 26 | fullMatcher: matcher, 27 | timeoutInterval: timeout, 28 | pollInterval: pollInterval)) 29 | } 30 | 31 | public func toEventuallyNot(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01) { 32 | toNot(AsyncMatcherWrapper( 33 | fullMatcher: matcher, 34 | timeoutInterval: timeout, 35 | pollInterval: pollInterval)) 36 | } 37 | 38 | public func toEventually(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01) { 39 | toEventually( 40 | FullMatcherWrapper( 41 | matcher: matcher, 42 | to: "to eventually", 43 | toNot: "to eventually not"), 44 | timeout: timeout, 45 | pollInterval: pollInterval) 46 | } 47 | 48 | public func toEventuallyNot(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01) { 49 | toEventuallyNot( 50 | FullMatcherWrapper( 51 | matcher: matcher, 52 | to: "to eventually", 53 | toNot: "to eventually not"), 54 | timeout: timeout, 55 | pollInterval: pollInterval) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Nimble/Matchers/MatcherProtocols.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | // Implement this protocol if you want full control over to() and toNot() behaviors 5 | public protocol Matcher { 6 | typealias ValueType 7 | func matches(actualExpression: Expression, failureMessage: FailureMessage) -> Bool 8 | func doesNotMatch(actualExpression: Expression, failureMessage: FailureMessage) -> Bool 9 | } 10 | 11 | // Implement this protocol if you just want a simplier matcher. The negation 12 | // is provided for you automatically. 13 | // 14 | // If you just want a very simplified usage of BasicMatcher, 15 | // @see MatcherFunc. 16 | public protocol BasicMatcher { 17 | typealias ValueType 18 | func matches(actualExpression: Expression, failureMessage: FailureMessage) -> Bool 19 | } 20 | 21 | // Objective-C interface to a similar interface 22 | @objc public protocol NMBMatcher { 23 | func matches(actualBlock: () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool 24 | } 25 | 26 | // Protocol for types that support contain() matcher 27 | @objc public protocol NMBContainer { 28 | func containsObject(object: AnyObject!) -> Bool 29 | } 30 | extension NSArray : NMBContainer {} 31 | extension NSSet : NMBContainer {} 32 | extension NSHashTable : NMBContainer {} 33 | 34 | // Protocol for types that support only beEmpty() 35 | @objc public protocol NMBCollection { 36 | var count: Int { get } 37 | } 38 | extension NSSet : NMBCollection {} 39 | extension NSDictionary : NMBCollection {} 40 | extension NSHashTable : NMBCollection {} 41 | 42 | // Protocol for types that support beginWith(), endWith(), beEmpty() matchers 43 | @objc public protocol NMBOrderedCollection : NMBCollection { 44 | func indexOfObject(object: AnyObject!) -> Int 45 | } 46 | extension NSArray : NMBOrderedCollection {} 47 | 48 | // Protocol for types to support beCloseTo() matcher 49 | @objc public protocol NMBDoubleConvertible { 50 | var doubleValue: CDouble { get } 51 | } 52 | extension NSNumber : NMBDoubleConvertible { } 53 | extension NSDecimalNumber : NMBDoubleConvertible { } // TODO: not the best to downsize 54 | 55 | // Protocol for types to support beLessThan(), beLessThanOrEqualTo(), 56 | // beGreaterThan(), beGreaterThanOrEqualTo(), and equal() matchers. 57 | // 58 | // Types that conform to Swift's Comparable protocol will work implicitly too 59 | @objc public protocol NMBComparable { 60 | func NMB_compare(otherObject: NMBComparable!) -> NSComparisonResult 61 | } 62 | extension NSNumber : NMBComparable { 63 | public func NMB_compare(otherObject: NMBComparable!) -> NSComparisonResult { 64 | return compare(otherObject as NSNumber) 65 | } 66 | } 67 | extension NSString : NMBComparable { 68 | public func NMB_compare(otherObject: NMBComparable!) -> NSComparisonResult { 69 | return compare(otherObject as NSString) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Nimble/Matchers/BeLogical.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func _beBool(#expectedValue: BooleanType, #stringValue: String, #falseMatchesNil: Bool) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.postfixMessage = "be \(stringValue)" 6 | let actual = actualExpression.evaluate() 7 | if expectedValue { 8 | return actual?.boolValue == expectedValue.boolValue 9 | } else if !falseMatchesNil { 10 | return actual != nil && actual!.boolValue != !expectedValue.boolValue 11 | } else { 12 | return actual?.boolValue != !expectedValue.boolValue 13 | } 14 | } 15 | } 16 | 17 | // mark: beTrue() / beFalse() 18 | 19 | public func beTrue() -> MatcherFunc { 20 | return equal(true).withFailureMessage { failureMessage in 21 | failureMessage.postfixMessage = "be true" 22 | } 23 | } 24 | 25 | public func beFalse() -> MatcherFunc { 26 | return equal(false).withFailureMessage { failureMessage in 27 | failureMessage.postfixMessage = "be false" 28 | } 29 | } 30 | 31 | // mark: beTruthy() / beFalsy() 32 | 33 | public func beTruthy() -> MatcherFunc { 34 | return _beBool(expectedValue: true, stringValue: "truthy", falseMatchesNil: true) 35 | } 36 | 37 | public func beFalsy() -> MatcherFunc { 38 | return _beBool(expectedValue: false, stringValue: "falsy", falseMatchesNil: true) 39 | } 40 | 41 | extension NMBObjCMatcher { 42 | public class func beTruthyMatcher() -> NMBObjCMatcher { 43 | return NMBObjCMatcher { actualBlock, failureMessage, location in 44 | let block = ({ (actualBlock() as? NSNumber)?.boolValue ?? false as BooleanType? }) 45 | let expr = Expression(expression: block, location: location) 46 | return beTruthy().matches(expr, failureMessage: failureMessage) 47 | } 48 | } 49 | 50 | public class func beFalsyMatcher() -> NMBObjCMatcher { 51 | return NMBObjCMatcher { actualBlock, failureMessage, location in 52 | let block = ({ (actualBlock() as? NSNumber)?.boolValue ?? false as BooleanType? }) 53 | let expr = Expression(expression: block, location: location) 54 | return beFalsy().matches(expr, failureMessage: failureMessage) 55 | } 56 | } 57 | 58 | public class func beTrueMatcher() -> NMBObjCMatcher { 59 | return NMBObjCMatcher { actualBlock, failureMessage, location in 60 | let block = ({ (actualBlock() as? NSNumber)?.boolValue as Bool? }) 61 | let expr = Expression(expression: block, location: location) 62 | return beTrue().matches(expr, failureMessage: failureMessage) 63 | } 64 | } 65 | 66 | public class func beFalseMatcher() -> NMBObjCMatcher { 67 | return NMBObjCMatcher { actualBlock, failureMessage, location in 68 | let block = ({ (actualBlock() as? NSNumber)?.boolValue as Bool? }) 69 | let expr = Expression(expression: block, location: location) 70 | return beFalse().matches(expr, failureMessage: failureMessage) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Nimble/objc/DSL.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | NIMBLE_EXPORT NMBExpectation *NMB_expect(id(^actualBlock)(), const char *file, unsigned int line) { 5 | return [[NMBExpectation alloc] initWithActualBlock:actualBlock 6 | negative:NO 7 | file:[[NSString alloc] initWithFormat:@"%s", file] 8 | line:line]; 9 | } 10 | 11 | NIMBLE_EXPORT id NMB_beAnInstanceOf(Class expectedClass) { 12 | return [NMBObjCMatcher beAnInstanceOfMatcher:expectedClass]; 13 | } 14 | 15 | NIMBLE_EXPORT id NMB_beAKindOf(Class expectedClass) { 16 | return [NMBObjCMatcher beAKindOfMatcher:expectedClass]; 17 | } 18 | 19 | NIMBLE_EXPORT NMBObjCBeCloseToMatcher *NMB_beCloseTo(NSNumber *expectedValue) { 20 | return [NMBObjCMatcher beCloseToMatcher:expectedValue within:0.001]; 21 | } 22 | 23 | NIMBLE_EXPORT id NMB_beginWith(id itemElementOrSubstring) { 24 | return [NMBObjCMatcher beginWithMatcher:itemElementOrSubstring]; 25 | } 26 | 27 | NIMBLE_EXPORT id NMB_beGreaterThan(NSNumber *expectedValue) { 28 | return [NMBObjCMatcher beGreaterThanMatcher:expectedValue]; 29 | } 30 | 31 | NIMBLE_EXPORT id NMB_beGreaterThanOrEqualTo(NSNumber *expectedValue) { 32 | return [NMBObjCMatcher beGreaterThanOrEqualToMatcher:expectedValue]; 33 | } 34 | 35 | NIMBLE_EXPORT id NMB_beIdenticalTo(id expectedInstance) { 36 | return [NMBObjCMatcher beIdenticalToMatcher:expectedInstance]; 37 | } 38 | 39 | NIMBLE_EXPORT id NMB_beLessThan(NSNumber *expectedValue) { 40 | return [NMBObjCMatcher beLessThanMatcher:expectedValue]; 41 | } 42 | 43 | NIMBLE_EXPORT id NMB_beLessThanOrEqualTo(NSNumber *expectedValue) { 44 | return [NMBObjCMatcher beLessThanOrEqualToMatcher:expectedValue]; 45 | } 46 | 47 | NIMBLE_EXPORT id NMB_beTruthy() { 48 | return [NMBObjCMatcher beTruthyMatcher]; 49 | } 50 | 51 | NIMBLE_EXPORT id NMB_beFalsy() { 52 | return [NMBObjCMatcher beFalsyMatcher]; 53 | } 54 | 55 | NIMBLE_EXPORT id NMB_beTrue() { 56 | return [NMBObjCMatcher beTrueMatcher]; 57 | } 58 | 59 | NIMBLE_EXPORT id NMB_beFalse() { 60 | return [NMBObjCMatcher beFalseMatcher]; 61 | } 62 | 63 | NIMBLE_EXPORT id NMB_beNil() { 64 | return [NMBObjCMatcher beNilMatcher]; 65 | } 66 | 67 | NIMBLE_EXPORT id NMB_contain(id itemOrSubstring) { 68 | return [NMBObjCMatcher containMatcher:itemOrSubstring]; 69 | } 70 | 71 | NIMBLE_EXPORT id NMB_endWith(id itemElementOrSubstring) { 72 | return [NMBObjCMatcher endWithMatcher:itemElementOrSubstring]; 73 | } 74 | 75 | NIMBLE_EXPORT id NMB_equal(id expectedValue) { 76 | return [NMBObjCMatcher equalMatcher:expectedValue]; 77 | } 78 | 79 | NIMBLE_EXPORT id NMB_match(id expectedValue) { 80 | return [NMBObjCMatcher matchMatcher:expectedValue]; 81 | } 82 | 83 | NIMBLE_EXPORT NMBObjCRaiseExceptionMatcher *NMB_raiseException() { 84 | return [NMBObjCMatcher raiseExceptionMatcher]; 85 | } 86 | 87 | -------------------------------------------------------------------------------- /Nimble.xcodeproj/xcshareddata/xcschemes/Nimble-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 72 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Nimble.xcodeproj/xcshareddata/xcschemes/Nimble-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 72 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Nimble/Matchers/RaisesException.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func _raiseExceptionMatcher(message: String, matches: (NSException?) -> Bool) -> MatcherFunc { 4 | return MatcherFunc { actualExpression, failureMessage in 5 | failureMessage.actualValue = nil 6 | failureMessage.postfixMessage = message 7 | 8 | // It would be better if this was part of Expression, but 9 | // Swift compiler crashes when expect() is inside a closure. 10 | var exception: NSException? 11 | var result: T? 12 | var capture = NMBExceptionCapture(handler: ({ e in 13 | exception = e 14 | }), finally: nil) 15 | 16 | capture.tryBlock { 17 | actualExpression.evaluate() 18 | return 19 | } 20 | return matches(exception) 21 | } 22 | } 23 | 24 | public func raiseException(#named: String, #reason: String?) -> MatcherFunc { 25 | var theReason = "" 26 | if let reason = reason { 27 | theReason = reason 28 | } 29 | return _raiseExceptionMatcher("raise exception named <\(named)> and reason <\(theReason)>") { 30 | exception in return exception?.name == named && exception?.reason == reason 31 | } 32 | } 33 | 34 | public func raiseException(#named: String) -> MatcherFunc { 35 | return _raiseExceptionMatcher("raise exception named <\(named)>") { 36 | exception in return exception?.name == named 37 | } 38 | } 39 | 40 | public func raiseException() -> MatcherFunc { 41 | return _raiseExceptionMatcher("raise any exception") { 42 | exception in return exception != nil 43 | } 44 | } 45 | 46 | @objc public class NMBObjCRaiseExceptionMatcher : NMBMatcher { 47 | var _name: String? 48 | var _reason: String? 49 | init(name: String?, reason: String?) { 50 | _name = name 51 | _reason = reason 52 | } 53 | 54 | public func matches(actualBlock: () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { 55 | let block: () -> Any? = ({ actualBlock(); return nil }) 56 | let expr = Expression(expression: block, location: location) 57 | if _name != nil && _reason != nil { 58 | return raiseException(named: _name!, reason: _reason).matches(expr, failureMessage: failureMessage) 59 | } else if _name != nil { 60 | return raiseException(named: _name!).matches(expr, failureMessage: failureMessage) 61 | } else { 62 | return raiseException().matches(expr, failureMessage: failureMessage) 63 | } 64 | } 65 | 66 | var named: (name: String) -> NMBObjCRaiseExceptionMatcher { 67 | return ({ name in 68 | return NMBObjCRaiseExceptionMatcher(name: name, reason: self._reason) 69 | }) 70 | } 71 | 72 | var reason: (reason: String?) -> NMBObjCRaiseExceptionMatcher { 73 | return ({ reason in 74 | return NMBObjCRaiseExceptionMatcher(name: self._name, reason: reason) 75 | }) 76 | } 77 | } 78 | 79 | extension NMBObjCMatcher { 80 | public class func raiseExceptionMatcher() -> NMBObjCRaiseExceptionMatcher { 81 | return NMBObjCRaiseExceptionMatcher(name: nil, reason: nil) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /NimbleTests/SynchronousTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class SynchronousTest: XCTestCase { 5 | func testFailAlwaysFails() { 6 | failsWithErrorMessage("My error message") { 7 | fail("My error message") 8 | } 9 | failsWithErrorMessage("fail() always fails") { 10 | fail() 11 | } 12 | } 13 | 14 | func testToMatchesIfMatcherReturnsTrue() { 15 | expect(1).to(MatcherFunc { expr, failure in true }) 16 | expect{1}.to(MatcherFunc { expr, failure in true }) 17 | } 18 | 19 | func testToProvidesActualValueExpression() { 20 | var value: Int? 21 | expect(1).to(MatcherFunc { expr, failure in value = expr.evaluate(); return true }) 22 | expect(value).to(equal(1)) 23 | } 24 | 25 | func testToProvidesAMemoizedActualValueExpression() { 26 | var callCount = 0 27 | expect{ callCount++ }.to(MatcherFunc { expr, failure in 28 | expr.evaluate() 29 | expr.evaluate() 30 | return true 31 | }) 32 | expect(callCount).to(equal(1)) 33 | } 34 | 35 | func testToProvidesAMemoizedActualValueExpressionIsEvaluatedAtMatcherControl() { 36 | var callCount = 0 37 | expect{ callCount++ }.to(MatcherFunc { expr, failure in 38 | expect(callCount).to(equal(0)) 39 | expr.evaluate() 40 | return true 41 | }) 42 | expect(callCount).to(equal(1)) 43 | } 44 | 45 | func testToMatchAgainstLazyProperties() { 46 | expect(ObjectWithLazyProperty().value).to(equal("hello")) 47 | expect(ObjectWithLazyProperty().value).toNot(equal("world")) 48 | expect(ObjectWithLazyProperty().anotherValue).to(equal("world")) 49 | expect(ObjectWithLazyProperty().anotherValue).toNot(equal("hello")) 50 | } 51 | 52 | // repeated tests from to() for toNot() 53 | func testToNotMatchesIfMatcherReturnsTrue() { 54 | expect(1).toNot(MatcherFunc { expr, failure in false }) 55 | expect{1}.toNot(MatcherFunc { expr, failure in false }) 56 | } 57 | 58 | func testToNotProvidesActualValueExpression() { 59 | var value: Int? 60 | expect(1).toNot(MatcherFunc { expr, failure in value = expr.evaluate(); return false }) 61 | expect(value).to(equal(1)) 62 | } 63 | 64 | func testToNotProvidesAMemoizedActualValueExpression() { 65 | var callCount = 0 66 | expect{ callCount++ }.toNot(MatcherFunc { expr, failure in 67 | expr.evaluate() 68 | expr.evaluate() 69 | return false 70 | }) 71 | expect(callCount).to(equal(1)) 72 | } 73 | 74 | func testToNotProvidesAMemoizedActualValueExpressionIsEvaluatedAtMatcherControl() { 75 | var callCount = 0 76 | expect{ callCount++ }.toNot(MatcherFunc { expr, failure in 77 | expect(callCount).to(equal(0)) 78 | expr.evaluate() 79 | return false 80 | }) 81 | expect(callCount).to(equal(1)) 82 | } 83 | 84 | func testToNotNegativeMatches() { 85 | failsWithErrorMessage("expected to not match, got <1>") { 86 | expect(1).toNot(MatcherFunc { expr, failure in true }) 87 | } 88 | } 89 | 90 | 91 | func testNotToMatchesLikeToNot() { 92 | expect(1).notTo(MatcherFunc { expr, failure in false }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [Welcome to Nimble!](#welcome-to-nimble!) 5 | - [Reporting Bugs](#reporting-bugs) 6 | - [Building the Project](#building-the-project) 7 | - [Pull Requests](#pull-requests) 8 | - [Style Conventions](#style-conventions) 9 | - [Core Members](#core-members) 10 | - [Code of Conduct](#code-of-conduct) 11 | 12 | 13 | 14 | # Welcome to Nimble! 15 | 16 | We're building a BDD framework for a new generation of Swift and 17 | Objective-C developers. 18 | 19 | Nimble should be easy to use and easy to maintain. Let's keep things 20 | simple and well-tested. 21 | 22 | **tl;dr:** If you've added a file to the project, make sure it's 23 | included in both the OS X and iOS targets. 24 | 25 | ## Reporting Bugs 26 | 27 | Nothing is off-limits. If you're having a problem, we want to hear about 28 | it. 29 | 30 | - See a crash? File an issue. 31 | - Code isn't compiling, but you don't know why? Sounds like you should 32 | submit a new issue, bud. 33 | - Went to the kitchen, only to forget why you went in the first place? 34 | Better submit an issue. 35 | 36 | ## Building the Project 37 | 38 | - Use `Nimble.xcproj` to work on Nimble. 39 | 40 | ## Pull Requests 41 | 42 | - Nothing is trivial. Submit pull requests for anything: typos, 43 | whitespace, you name it. 44 | - Not all pull requests will be merged, but all will be acknowledged. If 45 | no one has provided feedback on your request, ping one of the owners 46 | by name. 47 | - Make sure your pull request includes any necessary updates to the 48 | README or other documentation. 49 | - Be sure the unit tests for both the OS X and iOS targets of Nimble 50 | before submitting your pull request. You can run all the OS X unit tests 51 | using `./test.sh full`. Use `./test.sh` without `full` to run against only 52 | the latest of OS X and iOS. 53 | - If you've added a file to the project, make sure it's included in both 54 | the OS X and iOS targets. 55 | 56 | ### Style Conventions 57 | 58 | - Indent using 4 spaces. 59 | - Keep lines 100 characters or shorter. Break long statements into 60 | shorter ones over multiple lines. 61 | - In Objective-C, use `#pragma mark -` to mark public, internal, 62 | protocol, and superclass methods. 63 | 64 | ## Core Members 65 | 66 | If a few of your pull requests have been merged, and you'd like a 67 | controlling stake in the project, file an issue asking for write access 68 | to the repository. 69 | 70 | ### Code of Conduct 71 | 72 | Your conduct as a core member is your own responsibility, but here are 73 | some "ground rules": 74 | 75 | - Feel free to push whatever you want to master, and (if you have 76 | ownership permissions) to create any repositories you'd like. 77 | 78 | Ideally, however, all changes should be submitted as GitHub pull 79 | requests. No one should merge their own pull request, unless no 80 | other core members respond for at least a few days. 81 | 82 | If you'd like to create a new repository, it'd be nice if you created 83 | a GitHub issue and gathered some feedback first. 84 | 85 | - It'd be awesome if you could review, provide feedback on, and close 86 | issues or pull requests submitted to the project. Please provide kind, 87 | constructive feedback. Please don't be sarcastic or snarky. 88 | 89 | -------------------------------------------------------------------------------- /Nimble/Wrappers/ObjCMatcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ObjCMatcherWrapper : Matcher { 4 | let matcher: NMBMatcher 5 | let to: String 6 | let toNot: String 7 | 8 | func matches(actualExpression: Expression, failureMessage: FailureMessage) -> Bool { 9 | failureMessage.to = to 10 | let pass = matcher.matches(({ actualExpression.evaluate() }), failureMessage: failureMessage, location: actualExpression.location) 11 | return pass 12 | } 13 | 14 | func doesNotMatch(actualExpression: Expression, failureMessage: FailureMessage) -> Bool { 15 | failureMessage.to = toNot 16 | let pass = matcher.matches(({ actualExpression.evaluate() }), failureMessage: failureMessage, location: actualExpression.location) 17 | return !pass 18 | } 19 | } 20 | 21 | // Equivalent to Expectation, but simplified for ObjC objects only 22 | public class NMBExpectation : NSObject { 23 | let _actualBlock: () -> NSObject! 24 | var _negative: Bool 25 | let _file: String 26 | let _line: UInt 27 | var _timeout: NSTimeInterval = 1.0 28 | 29 | public init(actualBlock: () -> NSObject!, negative: Bool, file: String, line: UInt) { 30 | self._actualBlock = actualBlock 31 | self._negative = negative 32 | self._file = file 33 | self._line = line 34 | } 35 | 36 | public var withTimeout: (NSTimeInterval) -> NMBExpectation { 37 | return ({ timeout in self._timeout = timeout 38 | return self 39 | }) 40 | } 41 | 42 | public var to: (matcher: NMBMatcher) -> Void { 43 | return ({ matcher in 44 | expect(file: self._file, line: self._line){ self._actualBlock() as NSObject? }.to( 45 | ObjCMatcherWrapper(matcher: matcher, to: "to", toNot: "to not") 46 | ) 47 | }) 48 | } 49 | 50 | public var toNot: (matcher: NMBMatcher) -> Void { 51 | return ({ matcher in 52 | expect(file: self._file, line: self._line){ self._actualBlock() as NSObject? }.toNot( 53 | ObjCMatcherWrapper(matcher: matcher, to: "to", toNot: "to not") 54 | ) 55 | }) 56 | } 57 | 58 | public var notTo: (matcher: NMBMatcher) -> Void { return toNot } 59 | 60 | public var toEventually: (matcher: NMBMatcher) -> Void { 61 | return ({ matcher in 62 | expect(file: self._file, line: self._line){ self._actualBlock() as NSObject? }.toEventually( 63 | ObjCMatcherWrapper(matcher: matcher, to: "to", toNot: "to not"), 64 | timeout: self._timeout 65 | ) 66 | }) 67 | } 68 | 69 | public var toEventuallyNot: (matcher: NMBMatcher) -> Void { 70 | return ({ matcher in 71 | expect(file: self._file, line: self._line){ self._actualBlock() as NSObject? }.toEventuallyNot( 72 | ObjCMatcherWrapper(matcher: matcher, to: "to", toNot: "to not"), 73 | timeout: self._timeout 74 | ) 75 | }) 76 | } 77 | } 78 | 79 | @objc public class NMBObjCMatcher : NMBMatcher { 80 | let _matcher: (actualExpression: () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool 81 | init(matcher: (actualExpression: () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool) { 82 | self._matcher = matcher 83 | } 84 | 85 | public func matches(actualBlock: () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { 86 | return _matcher( 87 | actualExpression: ({ actualBlock() as NSObject? }), 88 | failureMessage: failureMessage, 89 | location: location) 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /Nimble/objc/DSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class NMBExpectation; 4 | @class NMBObjCBeCloseToMatcher; 5 | @class NMBObjCRaiseExceptionMatcher; 6 | @protocol NMBMatcher; 7 | 8 | 9 | #define NIMBLE_EXPORT FOUNDATION_EXPORT 10 | 11 | #ifdef NIMBLE_DISABLE_SHORT_SYNTAX 12 | #define NIMBLE_SHORT(PROTO, ORIGINAL) 13 | #else 14 | #define NIMBLE_SHORT(PROTO, ORIGINAL) FOUNDATION_STATIC_INLINE PROTO { return (ORIGINAL); } 15 | #endif 16 | 17 | NIMBLE_EXPORT NMBExpectation *NMB_expect(id(^actualBlock)(), const char *file, unsigned int line); 18 | 19 | NIMBLE_EXPORT id NMB_equal(id expectedValue); 20 | NIMBLE_SHORT(id equal(id expectedValue), 21 | NMB_equal(expectedValue)); 22 | 23 | NIMBLE_EXPORT NMBObjCBeCloseToMatcher *NMB_beCloseTo(NSNumber *expectedValue); 24 | NIMBLE_SHORT(NMBObjCBeCloseToMatcher *beCloseTo(id expectedValue), 25 | NMB_beCloseTo(expectedValue)); 26 | 27 | NIMBLE_EXPORT id NMB_beAnInstanceOf(Class expectedClass); 28 | NIMBLE_SHORT(id beAnInstanceOf(Class expectedClass), 29 | NMB_beAnInstanceOf(expectedClass)); 30 | 31 | NIMBLE_EXPORT id NMB_beAKindOf(Class expectedClass); 32 | NIMBLE_SHORT(id beAKindOf(Class expectedClass), 33 | NMB_beAKindOf(expectedClass)); 34 | 35 | NIMBLE_EXPORT id NMB_beginWith(id itemElementOrSubstring); 36 | NIMBLE_SHORT(id beginWith(id itemElementOrSubstring), 37 | NMB_beginWith(itemElementOrSubstring)); 38 | 39 | NIMBLE_EXPORT id NMB_beGreaterThan(NSNumber *expectedValue); 40 | NIMBLE_SHORT(id beGreaterThan(NSNumber *expectedValue), 41 | NMB_beGreaterThan(expectedValue)); 42 | 43 | NIMBLE_EXPORT id NMB_beGreaterThanOrEqualTo(NSNumber *expectedValue); 44 | NIMBLE_SHORT(id beGreaterThanOrEqualTo(NSNumber *expectedValue), 45 | NMB_beGreaterThanOrEqualTo(expectedValue)); 46 | 47 | NIMBLE_EXPORT id NMB_beIdenticalTo(id expectedInstance); 48 | NIMBLE_SHORT(id beIdenticalTo(id expectedInstance), 49 | NMB_beIdenticalTo(expectedInstance)); 50 | 51 | NIMBLE_EXPORT id NMB_beLessThan(NSNumber *expectedValue); 52 | NIMBLE_SHORT(id beLessThan(NSNumber *expectedValue), 53 | NMB_beLessThan(expectedValue)); 54 | 55 | NIMBLE_EXPORT id NMB_beLessThanOrEqualTo(NSNumber *expectedValue); 56 | NIMBLE_SHORT(id beLessThanOrEqualTo(NSNumber *expectedValue), 57 | NMB_beLessThanOrEqualTo(expectedValue)); 58 | 59 | NIMBLE_EXPORT id NMB_beTruthy(); 60 | NIMBLE_SHORT(id beTruthy(), 61 | NMB_beTruthy()); 62 | 63 | NIMBLE_EXPORT id NMB_beFalsy(); 64 | NIMBLE_SHORT(id beFalsy(), 65 | NMB_beFalsy()); 66 | 67 | NIMBLE_EXPORT id NMB_beTrue(); 68 | NIMBLE_SHORT(id beTrue(), 69 | NMB_beTrue()); 70 | 71 | NIMBLE_EXPORT id NMB_beFalse(); 72 | NIMBLE_SHORT(id beFalse(), 73 | NMB_beFalse()); 74 | 75 | NIMBLE_EXPORT id NMB_beNil(); 76 | NIMBLE_SHORT(id beNil(), 77 | NMB_beNil()); 78 | 79 | NIMBLE_EXPORT id NMB_contain(id itemOrSubstring); 80 | NIMBLE_SHORT(id contain(id itemOrSubstring), 81 | NMB_contain(itemOrSubstring)); 82 | 83 | NIMBLE_EXPORT id NMB_endWith(id itemElementOrSubstring); 84 | NIMBLE_SHORT(id endWith(id itemElementOrSubstring), 85 | NMB_endWith(itemElementOrSubstring)); 86 | 87 | NIMBLE_EXPORT NMBObjCRaiseExceptionMatcher *NMB_raiseException(); 88 | NIMBLE_SHORT(NMBObjCRaiseExceptionMatcher *raiseException(), 89 | NMB_raiseException()); 90 | 91 | NIMBLE_EXPORT id NMB_match(id expectedValue); 92 | NIMBLE_SHORT(id match(id expectedValue), 93 | NMB_match(expectedValue)); 94 | 95 | 96 | #ifndef NIMBLE_DISABLE_SHORT_SYNTAX 97 | #define expect(EXPR) NMB_expect(^id{ return (EXPR); }, __FILE__, __LINE__) 98 | #define expectAction(EXPR) NMB_expect(^id{ (EXPR); return nil; }, __FILE__, __LINE__) 99 | #endif 100 | -------------------------------------------------------------------------------- /NimbleTests/objc/CompatibilityTest.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | 5 | @interface CompatibilityTest : XCTestCase 6 | 7 | @end 8 | 9 | @implementation CompatibilityTest 10 | 11 | - (void)testBeAnInstanceOf { 12 | NSNull *obj = [NSNull null]; 13 | expect(obj).to(beAnInstanceOf([NSNull class])); 14 | expect(@1).toNot(beAnInstanceOf([NSNull class])); 15 | expect(nil).toNot(beAnInstanceOf([NSNull class])); 16 | } 17 | - (void)testBeAKindOf { 18 | NSMutableArray *array = [NSMutableArray array]; 19 | expect(array).to(beAKindOf([NSArray class])); 20 | expect(@1).toNot(beAKindOf([NSNull class])); 21 | expect(nil).toNot(beAKindOf([NSNull class])); 22 | } 23 | 24 | - (void)testBeCloseTo { 25 | expect(@1.2).to(beCloseTo(@1.2001)); 26 | expect(@1.2).to(beCloseTo(@2).within(10)); 27 | expect(nil).toNot(beCloseTo(@0)); 28 | } 29 | 30 | - (void)testBeginWith { 31 | expect(@"hello world!").to(beginWith(@"hello")); 32 | expect(@"hello world!").toNot(beginWith(@"world")); 33 | 34 | NSArray *array = @[@1, @2]; 35 | expect(array).to(beginWith(@1)); 36 | expect(array).toNot(beginWith(@2)); 37 | expect(nil).toNot(beginWith(@1)); 38 | } 39 | 40 | - (void)testBeGreaterThan { 41 | expect(@2).to(beGreaterThan(@1)); 42 | expect(@2).toNot(beGreaterThan(@2)); 43 | expect(nil).toNot(beGreaterThan(@(-1))); 44 | } 45 | 46 | - (void)testBeGreaterThanOrEqualTo { 47 | expect(@2).to(beGreaterThanOrEqualTo(@2)); 48 | expect(@2).toNot(beGreaterThanOrEqualTo(@3)); 49 | expect(nil).toNot(beGreaterThanOrEqualTo(@(-1))); 50 | } 51 | 52 | - (void)testBeIdenticalTo { 53 | NSNull *obj = [NSNull null]; 54 | expect(obj).to(beIdenticalTo([NSNull null])); 55 | expect(@2).toNot(beIdenticalTo(@3)); 56 | expect(nil).toNot(beIdenticalTo(nil)); 57 | expect(nil).toNot(beIdenticalTo(@1)); 58 | } 59 | 60 | - (void)testBeLessThan { 61 | expect(@2).to(beLessThan(@3)); 62 | expect(@2).toNot(beLessThan(@2)); 63 | expect(nil).toNot(beLessThan(@1)); 64 | } 65 | 66 | - (void)testBeLessThanOrEqualTo { 67 | expect(@2).to(beLessThanOrEqualTo(@2)); 68 | expect(@2).toNot(beLessThanOrEqualTo(@1)); 69 | expect(nil).toNot(beLessThan(@2)); 70 | } 71 | 72 | - (void)testBeTruthy { 73 | expect(@YES).to(beTruthy()); 74 | expect(@NO).toNot(beTruthy()); 75 | expect(nil).toNot(beTruthy()); 76 | } 77 | 78 | - (void)testBeFalsy { 79 | expect(@NO).to(beFalsy()); 80 | expect(@YES).toNot(beFalsy()); 81 | expect(nil).to(beFalsy()); 82 | } 83 | 84 | - (void)testBeTrue { 85 | expect(@YES).to(beTrue()); 86 | expect(@NO).toNot(beTrue()); 87 | expect(nil).toNot(beTrue()); 88 | } 89 | 90 | - (void)testBeFalse { 91 | expect(@NO).to(beFalse()); 92 | expect(@YES).toNot(beFalse()); 93 | expect(nil).toNot(beFalse()); 94 | } 95 | 96 | - (void)testBeNil { 97 | expect(nil).to(beNil()); 98 | expect(@NO).toNot(beNil()); 99 | } 100 | 101 | - (void)testContain { 102 | NSArray *array = @[@1, @2]; 103 | expect(array).to(contain(@1)); 104 | expect(array).toNot(contain(@"HI")); 105 | expect(nil).toNot(contain(@"hi")); 106 | } 107 | 108 | - (void)testEndWith { 109 | NSArray *array = @[@1, @2]; 110 | expect(@"hello world!").to(endWith(@"world!")); 111 | expect(@"hello world!").toNot(endWith(@"hello")); 112 | expect(array).to(endWith(@2)); 113 | expect(array).toNot(endWith(@1)); 114 | expect(nil).toNot(endWith(@1)); 115 | } 116 | 117 | - (void)testEqual { 118 | expect(@1).to(equal(@1)); 119 | expect(@1).toNot(equal(@2)); 120 | expect(@1).notTo(equal(@2)); 121 | expect(@"hello").to(equal(@"hello")); 122 | expect(nil).toNot(equal(nil)); 123 | } 124 | 125 | - (void)testMatch { 126 | expect(@"11:14").to(match(@"\\d{2}:\\d{2}")); 127 | expect(@"hello").toNot(match(@"\\d{2}:\\d{2}")); 128 | } 129 | 130 | - (void)testRaiseException { 131 | __block NSException *exception = [NSException exceptionWithName:NSInvalidArgumentException reason:@"No food" userInfo:nil]; 132 | expectAction([exception raise]).to(raiseException()); 133 | expectAction(exception).toNot(raiseException()); 134 | } 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /Nimble/Matchers/Equal.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | public func equal(expectedValue: T?) -> MatcherFunc { 5 | return MatcherFunc { actualExpression, failureMessage in 6 | failureMessage.postfixMessage = "equal <\(stringify(expectedValue))>" 7 | let matches = actualExpression.evaluate() == expectedValue && expectedValue != nil 8 | if expectedValue == nil || actualExpression.evaluate() == nil { 9 | failureMessage.postfixMessage += " (will not match nils, use beNil() instead)" 10 | return false 11 | } 12 | return matches 13 | } 14 | } 15 | 16 | // perhaps try to extend to SequenceOf or Sequence types instead of dictionaries 17 | public func equal(expectedValue: [T: C]?) -> MatcherFunc<[T: C]> { 18 | return MatcherFunc { actualExpression, failureMessage in 19 | failureMessage.postfixMessage = "equal <\(stringify(expectedValue))>" 20 | if expectedValue == nil || actualExpression.evaluate() == nil { 21 | failureMessage.postfixMessage += " (will not match nils, use beNil() instead)" 22 | return false 23 | } 24 | var expectedGen = expectedValue!.generate() 25 | var actualGen = actualExpression.evaluate()!.generate() 26 | 27 | var expectedItem = expectedGen.next() 28 | var actualItem = actualGen.next() 29 | var matches = elementsAreEqual(expectedItem, actualItem) 30 | while (matches && (actualItem != nil || expectedItem != nil)) { 31 | actualItem = actualGen.next() 32 | expectedItem = expectedGen.next() 33 | matches = elementsAreEqual(expectedItem, actualItem) 34 | } 35 | return matches 36 | } 37 | } 38 | 39 | // perhaps try to extend to SequenceOf or Sequence types instead of arrays 40 | public func equal(expectedValue: [T]?) -> MatcherFunc<[T]> { 41 | return MatcherFunc { actualExpression, failureMessage in 42 | failureMessage.postfixMessage = "equal <\(stringify(expectedValue))>" 43 | if expectedValue == nil || actualExpression.evaluate() == nil { 44 | failureMessage.postfixMessage += " (will not match nils, use beNil() instead)" 45 | return false 46 | } 47 | var expectedGen = expectedValue!.generate() 48 | var actualGen = actualExpression.evaluate()!.generate() 49 | var expectedItem = expectedGen.next() 50 | var actualItem = actualGen.next() 51 | var matches = actualItem == expectedItem 52 | while (matches && (actualItem != nil || expectedItem != nil)) { 53 | actualItem = actualGen.next() 54 | expectedItem = expectedGen.next() 55 | matches = actualItem == expectedItem 56 | } 57 | return matches 58 | } 59 | } 60 | 61 | public func ==(lhs: Expectation, rhs: T?) { 62 | lhs.to(equal(rhs)) 63 | } 64 | 65 | public func !=(lhs: Expectation, rhs: T?) { 66 | lhs.toNot(equal(rhs)) 67 | } 68 | 69 | public func ==(lhs: Expectation<[T]>, rhs: [T]?) { 70 | lhs.to(equal(rhs)) 71 | } 72 | 73 | public func !=(lhs: Expectation<[T]>, rhs: [T]?) { 74 | lhs.toNot(equal(rhs)) 75 | } 76 | 77 | public func ==(lhs: Expectation<[T: C]>, rhs: [T: C]?) { 78 | lhs.to(equal(rhs)) 79 | } 80 | 81 | public func !=(lhs: Expectation<[T: C]>, rhs: [T: C]?) { 82 | lhs.toNot(equal(rhs)) 83 | } 84 | 85 | extension NMBObjCMatcher { 86 | public class func equalMatcher(expected: NSObject) -> NMBMatcher { 87 | return NMBObjCMatcher { actualExpression, failureMessage, location in 88 | let expr = Expression(expression: actualExpression, location: location) 89 | return equal(expected).matches(expr, failureMessage: failureMessage) 90 | } 91 | } 92 | } 93 | 94 | 95 | internal func elementsAreEqual(a: (T, C)?, b: (T, C)?) -> Bool { 96 | if a == nil || b == nil { 97 | return a == nil && b == nil 98 | } else { 99 | let (aKey, aValue) = a! 100 | let (bKey, bValue) = b! 101 | return (aKey == bKey && aValue == bValue) 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/BeLogicalTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | enum ConvertsToBool : BooleanType, Printable { 5 | case TrueLike, FalseLike 6 | 7 | var boolValue : Bool { 8 | switch self { 9 | case .TrueLike: return true 10 | case .FalseLike: return false 11 | } 12 | } 13 | 14 | var description : String { 15 | switch self { 16 | case .TrueLike: return "TrueLike" 17 | case .FalseLike: return "FalseLike" 18 | } 19 | } 20 | } 21 | 22 | class BeTruthyTest : XCTestCase { 23 | func testShouldMatchTrue() { 24 | expect(true).to(beTruthy()) 25 | 26 | failsWithErrorMessage("expected to not be truthy, got ") { 27 | expect(true).toNot(beTruthy()) 28 | } 29 | } 30 | 31 | func testShouldNotMatchFalse() { 32 | expect(false).toNot(beTruthy()) 33 | 34 | failsWithErrorMessage("expected to be truthy, got ") { 35 | expect(false).to(beTruthy()) 36 | } 37 | } 38 | 39 | func testShouldNotMatchNilBools() { 40 | expect(nil as Bool?).toNot(beTruthy()) 41 | 42 | failsWithErrorMessage("expected to be truthy, got ") { 43 | expect(nil as Bool?).to(beTruthy()) 44 | } 45 | } 46 | 47 | func testShouldMatchBoolConvertibleTypesThatConvertToTrue() { 48 | expect(ConvertsToBool.TrueLike).to(beTruthy()) 49 | 50 | failsWithErrorMessage("expected to not be truthy, got ") { 51 | expect(ConvertsToBool.TrueLike).toNot(beTruthy()) 52 | } 53 | } 54 | 55 | func testShouldNotMatchBoolConvertibleTypesThatConvertToFalse() { 56 | expect(ConvertsToBool.FalseLike).toNot(beTruthy()) 57 | 58 | failsWithErrorMessage("expected to be truthy, got ") { 59 | expect(ConvertsToBool.FalseLike).to(beTruthy()) 60 | } 61 | } 62 | } 63 | 64 | class BeTrueTest : XCTestCase { 65 | func testShouldMatchTrue() { 66 | expect(true).to(beTrue()) 67 | 68 | failsWithErrorMessage("expected to not be true, got ") { 69 | expect(true).toNot(beTrue()) 70 | } 71 | } 72 | 73 | func testShouldNotMatchFalse() { 74 | expect(false).toNot(beTrue()) 75 | 76 | failsWithErrorMessage("expected to be true, got ") { 77 | expect(false).to(beTrue()) 78 | } 79 | } 80 | 81 | func testShouldNotMatchNilBools() { 82 | expect(nil as Bool?).toNot(beTrue()) 83 | 84 | failsWithErrorMessage("expected to be true, got ") { 85 | expect(nil as Bool?).to(beTrue()) 86 | } 87 | } 88 | } 89 | 90 | class BeFalsyTest : XCTestCase { 91 | func testShouldNotMatchTrue() { 92 | expect(true).toNot(beFalsy()) 93 | 94 | failsWithErrorMessage("expected to be falsy, got ") { 95 | expect(true).to(beFalsy()) 96 | } 97 | } 98 | 99 | func testShouldMatchFalse() { 100 | expect(false).to(beFalsy()) 101 | 102 | failsWithErrorMessage("expected to not be falsy, got ") { 103 | expect(false).toNot(beFalsy()) 104 | } 105 | } 106 | 107 | func testShouldMatchNilBools() { 108 | expect(nil as Bool?).to(beFalsy()) 109 | 110 | failsWithErrorMessage("expected to not be falsy, got ") { 111 | expect(nil as Bool?).toNot(beFalsy()) 112 | } 113 | } 114 | } 115 | 116 | class BeFalseTest : XCTestCase { 117 | func testShouldNotMatchTrue() { 118 | expect(true).toNot(beFalse()) 119 | 120 | failsWithErrorMessage("expected to be false, got ") { 121 | expect(true).to(beFalse()) 122 | } 123 | } 124 | 125 | func testShouldMatchFalse() { 126 | expect(false).to(beFalse()) 127 | 128 | failsWithErrorMessage("expected to not be false, got ") { 129 | expect(false).toNot(beFalse()) 130 | } 131 | } 132 | 133 | func testShouldNotMatchNilBools() { 134 | failsWithErrorMessage("expected to be false, got ") { 135 | expect(nil as Bool?).to(beFalse()) 136 | } 137 | 138 | failsWithErrorMessage("expected to not be false, got ") { 139 | expect(nil as Bool?).toNot(beFalse()) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /NimbleTests/Matchers/EqualTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Nimble 3 | 4 | class EqualTest: XCTestCase { 5 | func testEquality() { 6 | expect(1 as CInt).to(equal(1 as CInt)) 7 | expect(1 as CInt).to(equal(1)) 8 | expect(1).to(equal(1)) 9 | expect("hello").to(equal("hello")) 10 | expect("hello").toNot(equal("world")) 11 | 12 | expect { 13 | 1 14 | }.to(equal(1)) 15 | 16 | failsWithErrorMessage("expected to equal , got ") { 17 | expect("hello").to(equal("world")) 18 | } 19 | failsWithErrorMessage("expected to not equal , got ") { 20 | expect("hello").toNot(equal("hello")) 21 | } 22 | } 23 | 24 | func testArrayEquality() { 25 | expect([1, 2, 3]).to(equal([1, 2, 3])) 26 | expect([1, 2, 3]).toNot(equal([1, 2])) 27 | expect([1, 2, 3]).toNot(equal([1, 2, 4])) 28 | 29 | let array1: Array = [1, 2, 3] 30 | let array2: Array = [1, 2, 3] 31 | expect(array1).to(equal(array2)) 32 | expect(array1).to(equal([1, 2, 3])) 33 | expect(array1).toNot(equal([1, 2] as Array)) 34 | 35 | expect(NSArray(array: [1, 2, 3])).to(equal(NSArray(array: [1, 2, 3]))) 36 | 37 | failsWithErrorMessage("expected to equal <[1, 2]>, got <[1, 2, 3]>") { 38 | expect([1, 2, 3]).to(equal([1, 2])) 39 | } 40 | } 41 | 42 | func testDoesNotMatchNils() { 43 | failsWithErrorMessage("expected to equal (will not match nils, use beNil() instead), got ") { 44 | expect(nil as String?).to(equal(nil as String?)) 45 | } 46 | failsWithErrorMessage("expected to not equal (will not match nils, use beNil() instead), got ") { 47 | expect("foo").toNot(equal(nil as String?)) 48 | } 49 | failsWithErrorMessage("expected to not equal (will not match nils, use beNil() instead), got ") { 50 | expect(nil as String?).toNot(equal("bar")) 51 | } 52 | 53 | failsWithErrorMessage("expected to equal (will not match nils, use beNil() instead), got ") { 54 | expect(nil as [Int]?).to(equal(nil as [Int]?)) 55 | } 56 | failsWithErrorMessage("expected to not equal <[1]> (will not match nils, use beNil() instead), got ") { 57 | expect(nil as [Int]?).toNot(equal([1])) 58 | } 59 | failsWithErrorMessage("expected to not equal (will not match nils, use beNil() instead), got <[1]>") { 60 | expect([1]).toNot(equal(nil as [Int]?)) 61 | } 62 | 63 | failsWithErrorMessage("expected to equal (will not match nils, use beNil() instead), got ") { 64 | expect(nil as [Int: Int]?).to(equal(nil as [Int: Int]?)) 65 | } 66 | failsWithErrorMessage("expected to not equal <[1: 1]> (will not match nils, use beNil() instead), got ") { 67 | expect(nil as [Int: Int]?).toNot(equal([1: 1])) 68 | } 69 | failsWithErrorMessage("expected to not equal (will not match nils, use beNil() instead), got <[1: 1]>") { 70 | expect([1: 1]).toNot(equal(nil as [Int: Int]?)) 71 | } 72 | } 73 | 74 | func testDictionaryEquality() { 75 | expect(["foo": "bar"]).to(equal(["foo": "bar"])) 76 | expect(["foo": "bar"]).toNot(equal(["foo": "baz"])) 77 | 78 | let actual = ["foo": "bar"] 79 | let expected = ["foo": "bar"] 80 | let unexpected = ["foo": "baz"] 81 | expect(actual).to(equal(expected)) 82 | expect(actual).toNot(equal(unexpected)) 83 | 84 | expect(NSDictionary(object: "bar", forKey: "foo")).to(equal(["foo": "bar"])) 85 | expect(NSDictionary(object: "bar", forKey: "foo")).to(equal(expected)) 86 | } 87 | 88 | func testNSObjectEquality() { 89 | expect(NSNumber(integer:1)).to(equal(NSNumber(integer:1))) 90 | expect(NSNumber(integer:1)) == NSNumber(integer:1) 91 | expect(NSNumber(integer:1)) != NSNumber(integer:2) 92 | expect { NSNumber(integer:1) }.to(equal(1)) 93 | } 94 | 95 | func testOperatorEquality() { 96 | expect("foo") == "foo" 97 | expect("foo") != "bar" 98 | 99 | failsWithErrorMessage("expected to equal , got ") { 100 | expect("hello") == "world" 101 | return 102 | } 103 | failsWithErrorMessage("expected to not equal , got ") { 104 | expect("hello") != "hello" 105 | return 106 | } 107 | } 108 | 109 | func testOperatorEqualityWithArrays() { 110 | let array1: Array = [1, 2, 3] 111 | let array2: Array = [1, 2, 3] 112 | let array3: Array = [1, 2] 113 | expect(array1) == array2 114 | expect(array1) != array3 115 | } 116 | 117 | func testOperatorEqualityWithDictionaries() { 118 | let dict1 = ["foo": "bar"] 119 | let dict2 = ["foo": "bar"] 120 | let dict3 = ["foo": "baz"] 121 | expect(dict1) == dict2 122 | expect(dict1) != dict3 123 | } 124 | 125 | func testOptionalEquality() { 126 | expect(1 as CInt?).to(equal(1)) 127 | expect(1 as CInt?).to(equal(1 as CInt?)) 128 | expect(nil as NSObject?).toNot(equal(1)) 129 | 130 | expect(1).toNot(equal(nil)) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014 Quick Team 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nimble 2 | 3 | [![Build Status](https://travis-ci.org/Quick/Nimble.svg?branch=master)](https://travis-ci.org/Quick/Nimble) 4 | 5 | Use Nimble to express the expected outcomes of Swift 6 | or Objective-C expressions. Inspired by 7 | [Cedar](https://github.com/pivotal/cedar). 8 | 9 | ```swift 10 | // Swift 11 | 12 | expect(1 + 1).to(equal(2)) 13 | expect(1.2).to(beCloseTo(1.1, within: 0.1)) 14 | expect(3) > 2 15 | expect("seahorse").to(contain("sea")) 16 | expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi")) 17 | expect(ocean.isClean).toEventually(beTruthy()) 18 | ``` 19 | 20 | # How to Use Nimble 21 | 22 | 23 | 24 | 25 | - [Some Background: Expressing Outcomes Using Assertions in XCTest](#some-background-expressing-outcomes-using-assertions-in-xctest) 26 | - [Nimble: Expectations Using `expect(...).to`](#nimble-expectations-using-expectto) 27 | - [Type Checking](#type-checking) 28 | - [Operator Overloads](#operator-overloads) 29 | - [Lazily Computed Values](#lazily-computed-values) 30 | - [C Primitives](#c-primitives) 31 | - [Asynchronous Expectations](#asynchronous-expectations) 32 | - [Objective-C Support](#objective-c-support) 33 | - [Disabling Objective-C Shorthand](#disabling-objective-c-shorthand) 34 | - [Built-in Matcher Functions](#built-in-matcher-functions) 35 | - [Equivalence](#equivalence) 36 | - [Identity](#identity) 37 | - [Comparisons](#comparisons) 38 | - [Types/Classes](#typesclasses) 39 | - [Truthiness](#truthiness) 40 | - [Exceptions](#exceptions) 41 | - [Collection Membership](#collection-membership) 42 | - [Strings](#strings) 43 | - [Writing Your Own Matchers](#writing-your-own-matchers) 44 | - [Lazy Evaluation](#lazy-evaluation) 45 | - [Type Checking via Swift Generics](#type-checking-via-swift-generics) 46 | - [Customizing Failure Messages](#customizing-failure-messages) 47 | - [Supporting Objective-C](#supporting-objective-c) 48 | - [Properly Handling `nil` in Objective-C Matchers](#properly-handling-nil-in-objective-c-matchers) 49 | - [Installing Nimble](#installing-nimble) 50 | 51 | 52 | 53 | # Some Background: Expressing Outcomes Using Assertions in XCTest 54 | 55 | Apple's Xcode includes the XCTest framework, which provides 56 | assertion macros to test whether code behaves properly. 57 | For example, to assert that `1 + 1 = 2`, XCTest has you write: 58 | 59 | ```swift 60 | // Swift 61 | 62 | XCTAssertEqual(1 + 1, 2, "expected one plus one to equal two") 63 | ``` 64 | 65 | Or, in Objective-C: 66 | 67 | ```objc 68 | // Objective-C 69 | 70 | XCTAssertEqual(1 + 1, 2, @"expected one plus one to equal two"); 71 | ``` 72 | 73 | XCTest assertions have several drawbacks: 74 | 75 | 1. **Not enough macros.** There's no easy way to assert that a string 76 | contains a particular substring, or that a number is less than or 77 | equal to another. 78 | 2. **No type checking.** It doesn't make sense to comapre a number to 79 | a string, but XCTest assertions allow it. Assertions are implemented 80 | using macros, so there's no type checking. 81 | 82 | `XCTAssertEqual(1 + 1, "2")` compiles and runs--you won't 83 | find out that you've made an impossible comparison until the test 84 | runs and fails. That could take seconds, or even minutes in larger test 85 | suites. 86 | 3. **It's hard to write asynchronous tests.** XCTest forces you to write 87 | a lot of boilerplate code. 88 | 89 | Nimble addresses all three of these concerns. 90 | 91 | # Nimble: Expectations Using `expect(...).to` 92 | 93 | Nimble allows you to express expectations using a natural, 94 | easily understood language: 95 | 96 | ```swift 97 | // Swift 98 | 99 | import Nimble 100 | 101 | expect(seagull.squawk).to(equal("Squee!")) 102 | ``` 103 | 104 | ```objc 105 | // Objective-C 106 | 107 | #import 108 | 109 | expect(seagull.squawk).to(equal(@"Squee!")); 110 | ``` 111 | 112 | > The `expect` function autocompletes to include `file:` and `line:`, 113 | but these parameters are optional. Use the default values to have 114 | Xcode highlight the correct line when an expectation is not met. 115 | 116 | To perform the opposite expectation--to assert something is *not* 117 | equal--use `toNot` or `notTo`: 118 | 119 | ```swift 120 | // Swift 121 | 122 | import Nimble 123 | 124 | expect(seagull.squawk).toNot(equal("Oh, hello there!")) 125 | expect(seagull.squawk).notTo(equal("Oh, hello there!")) 126 | ``` 127 | 128 | ```objc 129 | // Objective-C 130 | 131 | #import 132 | 133 | expect(seagull.squawk).toNot(equal(@"Oh, hello there!")); 134 | expect(seagull.squawk).notTo(equal(@"Oh, hello there!")); 135 | ``` 136 | 137 | ## Type Checking 138 | 139 | Nimble makes sure you don't compare two types that don't match: 140 | 141 | ```swift 142 | // Swift 143 | 144 | // Does not compile: 145 | expect(1 + 1).to(equal("Squee!")) 146 | ``` 147 | 148 | > Nimble uses generics--only available in Swift--to ensure 149 | type correctness. That means type checking is 150 | not available when using Nimble in Objective-C. :sob: 151 | 152 | ## Operator Overloads 153 | 154 | Tired of so much typing? With Nimble, you can use overloaded operators 155 | like `==` for equivalence, or `>` for comparisons: 156 | 157 | ```swift 158 | // Swift 159 | 160 | // Passes if squawk does not equal "Hi!": 161 | expect(seagull.squawk) != "Hi!" 162 | 163 | // Passes if 10 is greater than 2: 164 | expect(10) > 2 165 | ``` 166 | 167 | > Operator overloads are only available in Swift, so you won't be able 168 | to use this syntax in Objective-C. :broken_heart: 169 | 170 | ## Lazily Computed Values 171 | 172 | The `expect` function doesn't evalaute the value it's given until it's 173 | time to match. So Nimble can test whether an expression raises an 174 | exception once evaluated: 175 | 176 | ```swift 177 | // Swift 178 | 179 | let exception = NSException( 180 | name: NSInternalInconsistencyException, 181 | reason: "Not enough fish in the sea.", 182 | userInfo: nil) 183 | expect(exception.raise()).to(raiseException()) 184 | ``` 185 | 186 | Objective-C works the same way, but you must use the `expectAction` 187 | macro when making an expectation on an expression that has no return 188 | value: 189 | 190 | ```objc 191 | // Objective-C 192 | 193 | NSException *exception = [NSException exceptionWithName:NSInternalInconsistencyException 194 | reason:@"Not enough fish in the sea." 195 | userInfo:nil]; 196 | expectAction([exception raise]).to(raiseException()); 197 | ``` 198 | 199 | In Swift, the `expect` function can also take a trailing closure: 200 | 201 | ```swift 202 | // Swift 203 | 204 | expect { 205 | exception.raise() 206 | }.to(raiseException(named: NSInternalInconsistencyException)) 207 | ``` 208 | 209 | ## C Primitives 210 | 211 | Some testing frameworks make it hard to test primitive C values. 212 | In Nimble, it just works: 213 | 214 | ```swift 215 | // Swift 216 | 217 | let actual: CInt = 1 218 | let expectedValue: CInt = 1 219 | expect(actual).to(equal(expectedValue)) 220 | ``` 221 | 222 | In fact, Nimble uses type inference, so you can write the above 223 | without explicitly specifying both types: 224 | 225 | ```swift 226 | // Swift 227 | 228 | expect(1 as CInt).to(equal(1)) 229 | ``` 230 | 231 | > In Objective-C, Nimble only supports Objective-C objects. To 232 | make expectations on primitive C values, wrap then in an object 233 | literal: 234 | 235 | ```objc 236 | expect(@(1 + 1)).to(equal(@2)); 237 | ``` 238 | 239 | ## Asynchronous Expectations 240 | 241 | In Nimble, it's easy to make expectations on values that are updated 242 | asynchronously. Just use `toEventually` or `toEventuallyNot`: 243 | 244 | ```swift 245 | // Swift 246 | 247 | dispatch_async(dispatch_get_main_queue()) { 248 | ocean.add("dolphins"") 249 | ocean.add("whales") 250 | } 251 | expect(ocean).toEventually(contain("dolphins", "whales")) 252 | ``` 253 | 254 | 255 | ```objc 256 | // Objective-C 257 | dispatch_async(dispatch_get_main_queue(), ^{ 258 | [ocean add:@"dolphins"]; 259 | [ocean add:@"whales"]; 260 | }); 261 | expect(ocean).toEventually(contain(@"dolphins", @"whales")); 262 | ``` 263 | 264 | In the above example, `ocean` is constantly re-evaluated. If it ever 265 | contains dolphins and whales, the expectation passes. If `ocean` still 266 | doesn't contain them, even after being continuously re-evaluated for one 267 | whole second, the expectation fails. 268 | 269 | Sometimes it takes more than a second for a value to update. In those 270 | cases, use the `timeout` parameter: 271 | 272 | ```swift 273 | // Swift 274 | 275 | // Waits three seconds for ocean to contain "starfish": 276 | expect(ocean).toEventually(contain("starfish"), timeout: 3) 277 | ``` 278 | 279 | > Sorry, [Nimble doesn't support specifying custom timeouts in Objective-C yet](https://github.com/Quick/Nimble/issues/25). 280 | 281 | You can also provide a callback by using the `waitUntil` function: 282 | 283 | ```swift 284 | // Swift 285 | 286 | waitUntil { done in 287 | // do some stuff that takes a while... 288 | NSThread.sleepForTimeInterval(0.5) 289 | done() 290 | } 291 | ``` 292 | 293 | `waitUntil` also optionally takes a timeout parameter: 294 | 295 | ```swift 296 | // Swift 297 | 298 | waitUntil(timeout: 10) { done in 299 | // do some stuff that takes a while... 300 | NSThread.sleepForTimeInterval(1) 301 | done() 302 | } 303 | ``` 304 | 305 | ## Objective-C Support 306 | 307 | Nimble has full support for Objective-C. However, there are two things 308 | to keep in mind when using Nimble in Objective-C: 309 | 310 | 1. All parameters passed to the `expect` function, as well as matcher 311 | functions like `equal`, must be Objective-C objects: 312 | 313 | ```objc 314 | // Objective-C 315 | 316 | #import 317 | 318 | expect(@(1 + 1)).to(equal(@2)); 319 | expect(@"Hello world").to(contain(@"world")); 320 | ``` 321 | 322 | 2. To make an expectation on an expression that does not return a value, 323 | such as `-[NSException raise]`, use `expectAction` instead of 324 | `expect`: 325 | 326 | ```objc 327 | // Objective-C 328 | 329 | expectAction([exception raise]).to(raiseException()); 330 | ``` 331 | 332 | ## Disabling Objective-C Shorthand 333 | 334 | Nimble provides a shorthand for expressing expectations using the 335 | `expect` function. To disable this shorthand in Objective-C, define the 336 | `NIMBLE_DISABLE_SHORT_SYNTAX` macro somewhere in your code before 337 | importing Nimble: 338 | 339 | ```objc 340 | #define NIMBLE_DISABLE_SHORT_SYNTAX 1 341 | 342 | #import 343 | 344 | NMB_expect(^{ return seagull.squawk; }, __FILE__, __LINE__).to(NMB_equal(@"Squee!")); 345 | ``` 346 | 347 | > Disabling the shorthand is useful if you're testing functions with 348 | names that conflict with Nimble functions, such as `expect` or 349 | `equal`. If that's not the case, there's no point in disabling the 350 | shorthand. 351 | 352 | # Built-in Matcher Functions 353 | 354 | Nimble includes a wide variety of matcher functions. 355 | 356 | ## Equivalence 357 | 358 | ```swift 359 | // Swift 360 | 361 | // Passes if actual is equivalent to expected: 362 | expect(actual).to(equal(expected)) 363 | expect(actual) == expected 364 | 365 | // Passes if actual is not equivalent to expected: 366 | expect(actual).toNot(equal(expected)) 367 | expect(actual) != expected 368 | ``` 369 | 370 | ```objc 371 | // Objective-C 372 | 373 | // Passes if actual is equivalent to expected: 374 | expect(actual).to(equal(expected)) 375 | 376 | // Passes if actual is not equivalent to expected: 377 | expect(actual).toNot(equal(expected)) 378 | ``` 379 | 380 | Values must be `Equatable`, `Comparable`, or subclasses of `NSObject`. 381 | `equal` will always fail when used to compare one or more `nil` values. 382 | 383 | ## Identity 384 | 385 | ```swift 386 | // Swift 387 | 388 | // Passes if actual has the same pointer address as expected: 389 | expect(actual).to(beIdenticalTo(expected)) 390 | expect(actual) === expected 391 | 392 | // Passes if actual does not have the same pointer address as expected: 393 | expect(actual).toNot(beIdenticalTo(expected)) 394 | expect(actual) !== expected 395 | ``` 396 | 397 | ```objc 398 | // Objective-C 399 | 400 | // Passes if actual has the same pointer address as expected: 401 | expect(actual).to(beIdenticalTo(expected)); 402 | 403 | // Passes if actual does not have the same pointer address as expected: 404 | expect(actual).toNot(beIdenticalTo(expected)); 405 | ``` 406 | 407 | > `beIdenticalTo` only supports Objective-C objects: subclasses 408 | of `NSObject`, or Swift objects bridged to Objective-C with the 409 | `@objc` prefix. 410 | 411 | ## Comparisons 412 | 413 | ```swift 414 | // Swift 415 | 416 | expect(actual).to(beLessThan(expected)) 417 | expect(actual) < expected 418 | 419 | expect(actual).to(beLessThanOrEqualTo(expected)) 420 | expect(actual) <= expected 421 | 422 | expect(actual).to(beGreaterThan(expected)) 423 | expect(actual) > expected 424 | 425 | expect(actual).to(beGreaterThanOrEqualTo(expected)) 426 | expect(actual) >= expected 427 | ``` 428 | 429 | ```objc 430 | // Objective-C 431 | 432 | expect(actual).to(beLessThan(expected)); 433 | expect(actual).to(beLessThanOrEqualTo(expected)); 434 | expect(actual).to(beGreaterThan(expected)); 435 | expect(actual).to(beGreaterThanOrEqualTo(expected)); 436 | ``` 437 | 438 | > Values given to the comparison matchers above must implement 439 | `Comparable`. 440 | 441 | Because of how computers represent floating point numbers, assertions 442 | that two floating point numbers be equal will sometimes fail. To express 443 | that two numbers should be close to one another within a certain margin 444 | of error, use `beCloseTo`: 445 | 446 | ```swift 447 | // Swift 448 | 449 | expect(actual).to(beCloseTo(expected, within: delta)) 450 | ``` 451 | 452 | ```objc 453 | // Objective-C 454 | 455 | expect(actual).to(beCloseTo(expected).within(delta)); 456 | ``` 457 | 458 | For example, to assert that `10.01` is close to `10`, you can write: 459 | 460 | ```swift 461 | // Swift 462 | 463 | expect(10.01).to(beCloseTo(10, within: 0.1)) 464 | ``` 465 | 466 | ```objc 467 | // Objective-C 468 | 469 | expect(@(10.01)).to(beCloseTo(@10).within(0.1)); 470 | ``` 471 | 472 | > Values given to the `beCloseTo` matcher must be coercable into a 473 | `Double`. 474 | 475 | ## Types/Classes 476 | 477 | ```swift 478 | // Swift 479 | 480 | // Passes if instance is an instance of aClass: 481 | expect(instance).to(beAnInstanceOf(aClass)) 482 | 483 | // Passes if instance is an instance of aClass or any of its subclasses: 484 | expect(instance).to(beAKindOf(aClass)) 485 | ``` 486 | 487 | ```objc 488 | // Objective-C 489 | 490 | // Passes if instance is an instance of aClass: 491 | expect(instance).to(beAnInstanceOf(aClass)); 492 | 493 | // Passes if instance is an instance of aClass or any of its subclasses: 494 | expect(instance).to(beAKindOf(aClass)); 495 | ``` 496 | 497 | > Instances must be Objective-C objects: subclasses of `NSObject`, 498 | or Swift objects bridged to Objective-C with the `@objc` prefix. 499 | 500 | For example, to assert that `dolphin` is a kind of `Mammal`: 501 | 502 | ```swift 503 | // Swift 504 | 505 | expect(dolphin).to(beAKindOf(Mammal)) 506 | ``` 507 | 508 | ```objc 509 | // Objective-C 510 | 511 | expect(dolphin).to(beAKindOf([Mammal class])); 512 | ``` 513 | 514 | > `beAnInstanceOf` uses the `-[NSObject isMemberOfClass:]` method to 515 | test membership. `beAKindOf` uses `-[NSObject isKindOfClass:]`. 516 | 517 | ## Truthiness 518 | 519 | ```swift 520 | // Passes if actual is not nil, false, or an object with a boolean value of false: 521 | expect(actual).to(beTruthy()) 522 | 523 | // Passes if actual is only true (not nil or an object conforming to BooleanType true): 524 | expect(actual).to(beTrue()) 525 | 526 | // Passes if actual is nil, false, or an object with a boolean value of false: 527 | expect(actual).to(beFalsy()) 528 | 529 | // Passes if actual is only false (not nil or an object conforming to BooleanType false): 530 | expect(actual).to(beFalse()) 531 | 532 | // Passes if actual is nil: 533 | expect(actual).to(beNil()) 534 | ``` 535 | 536 | ```objc 537 | // Objective-C 538 | 539 | // Passes if actual is not nil, false, or an object with a boolean value of false: 540 | expect(actual).to(beTruthy()); 541 | 542 | // Passes if actual is only true (not nil or an object conforming to BooleanType true): 543 | expect(actual).to(beTrue()); 544 | 545 | // Passes if actual is nil, false, or an object with a boolean value of false: 546 | expect(actual).to(beFalsy()); 547 | 548 | // Passes if actual is only false (not nil or an object conforming to BooleanType false): 549 | expect(actual).to(beFalse()); 550 | 551 | // Passes if actual is nil: 552 | expect(actual).to(beNil()); 553 | ``` 554 | 555 | ## Exceptions 556 | 557 | ```swift 558 | // Swift 559 | 560 | // Passes if actual, when evaluated, raises an exception: 561 | expect(actual).to(raiseException()) 562 | 563 | // Passes if actual raises an exception with the given name: 564 | expect(actual).to(raiseException(named: name)) 565 | 566 | // Passes if actual raises an exception with the given name and reason: 567 | expect(actual).to(raiseException(named: name, reason: reason)) 568 | ``` 569 | 570 | ```objc 571 | // Objective-C 572 | 573 | // Passes if actual, when evaluated, raises an exception: 574 | expect(actual).to(raiseException()) 575 | ``` 576 | 577 | > Sorry, [Nimble doesn't support matching on exception `name`, `reason`, or `userInfo` yet](https://github.com/Quick/Nimble/issues/26). 578 | 579 | ## Collection Membership 580 | 581 | ```swift 582 | // Swift 583 | 584 | // Passes if all of the expected values are members of actual: 585 | expect(actual).to(contain(expected...)) 586 | 587 | // Passes if actual is an empty collection (it contains no elements): 588 | expect(actual).to(beEmpty()) 589 | ``` 590 | 591 | ```objc 592 | // Objective-C 593 | 594 | // Passes if expected is a member of actual: 595 | expect(actual).to(contain(expected)); 596 | 597 | // Passes if actual is an empty collection (it contains no elements): 598 | expect(actual).to(beEmpty()); 599 | ``` 600 | 601 | > In Swift `contain` takes any number of arguments. The expectation 602 | passes if all of them are members of the collection. In Objective-C, 603 | `contain` only takes one argument [for now](https://github.com/Quick/Nimble/issues/27). 604 | 605 | For example, to assert that a list of sea creature names contains 606 | "dolphin" and "starfish": 607 | 608 | ```swift 609 | // Swift 610 | 611 | expect(["whale", "dolphin", "starfish"]).to(contain("dolphin", "starfish")) 612 | ``` 613 | 614 | ```objc 615 | // Objective-C 616 | 617 | expect(@[@"whale", @"dolphin", @"starfish"]).to(contain(@"dolphin")); 618 | expect(@[@"whale", @"dolphin", @"starfish"]).to(contain(@"starfish")); 619 | ``` 620 | 621 | > `contain` and `beEmpty` expect collections to be instances of 622 | `NSArray`, `NSSet`, or a Swift collection composed of `Equatable` elements. 623 | 624 | To test whether a set of elements is present at the beginning or end of 625 | an ordered collection, use `beginWith` and `endWith`: 626 | 627 | ```swift 628 | // Swift 629 | 630 | // Passes if the elements in expected appear at the beginning of actual: 631 | expect(actual).to(beginWith(expected...)) 632 | 633 | // Passes if the the elements in expected come at the end of actual: 634 | expect(actual).to(endWith(expected...)) 635 | ``` 636 | 637 | ```objc 638 | // Objective-C 639 | 640 | // Passes if the elements in expected appear at the beginning of actual: 641 | expect(actual).to(beginWith(expected)); 642 | 643 | // Passes if the the elements in expected come at the end of actual: 644 | expect(actual).to(endWith(expected)); 645 | ``` 646 | 647 | > `beginWith` and `endWith` expect collections to be instances of 648 | `NSArray`, or ordered Swift collections composed of `Equatable` 649 | elements. 650 | 651 | Like `contain`, in Objective-C `beginWith` and `endWith` only support 652 | a single argument [for now](https://github.com/Quick/Nimble/issues/27). 653 | 654 | ## Strings 655 | 656 | ```swift 657 | // Swift 658 | 659 | // Passes if actual contains substring expected: 660 | expect(actual).to(contain(expected)) 661 | 662 | // Passes if actual begins with substring: 663 | expect(actual).to(beginWith(expected)) 664 | 665 | // Passes if actual ends with substring: 666 | expect(actual).to(endWith(expected)) 667 | 668 | // Passes if actual is an empty string, "": 669 | expect(actual).to(beEmpty()) 670 | 671 | // Passes if actual matches the regular expression defined in expected: 672 | expect(actual).to(match(expected)) 673 | ``` 674 | 675 | ```objc 676 | // Objective-C 677 | 678 | // Passes if actual contains substring expected: 679 | expect(actual).to(contain(expected)); 680 | 681 | // Passes if actual begins with substring: 682 | expect(actual).to(beginWith(expected)); 683 | 684 | // Passes if actual ends with substring: 685 | expect(actual).to(endWith(expected)); 686 | 687 | // Passes if actual is an empty string, "": 688 | expect(actual).to(beEmpty()); 689 | 690 | // Passes if actual matches the regular expression defined in expected: 691 | expect(actual).to(match(expected)) 692 | ``` 693 | 694 | # Writing Your Own Matchers 695 | 696 | In Nimble, matchers are Swift functions that take an expected 697 | value and return a `MatcherFunc` closure. Take `equal`, for example: 698 | 699 | ```swift 700 | // Swift 701 | 702 | public func equal(expectedValue: T?) -> MatcherFunc { 703 | return MatcherFunc { actualExpression, failureMessage in 704 | failureMessage.postfixMessage = "equal <\(expectedValue)>" 705 | return actualExpression.evaluate() == expectedValue 706 | } 707 | } 708 | ``` 709 | 710 | The return value of a `MatcherFunc` closure is a `Bool` that indicates 711 | whether the actual value matches the expectation: `true` if it does, or 712 | `false` if it doesn't. 713 | 714 | > The actual `equal` matcher function does not match when either 715 | `actual` or `expected` are nil; the example above has been edited for 716 | brevity. 717 | 718 | Since matchers are just Swift functions, you can define them anywhere: 719 | at the top of your test file, in a file shared by all of your tests, or 720 | in an Xcode project you distribute to others. 721 | 722 | > If you write a matcher you think everyone can use, consider adding it 723 | to Nimble's built-in set of matchers by sending a pull request! Or 724 | distribute it yourself via GitHub. 725 | 726 | For examples of how to write your own matchers, just check out the 727 | [`Matchers` directory](https://github.com/Quick/Nimble/tree/master/Nimble/Matchers) 728 | to see how Nimble's built-in set of matchers are implemented. You can 729 | also check out the tips below. 730 | 731 | ## Lazy Evaluation 732 | 733 | `actualExpression` is a lazy, memoized closure around the value provided to 734 | the `expect` function. In order to determine whether that value matches, 735 | custom matchers should call `actualExpression.evalaute()`: 736 | 737 | ```swift 738 | // Swift 739 | 740 | public func beNil() -> MatcherFunc { 741 | return MatcherFunc { actualExpression, failureMessage in 742 | failureMessage.postfixMessage = "be nil" 743 | return actualExpression.evaluate() == nil 744 | } 745 | } 746 | ``` 747 | 748 | In the above example, `actualExpression` is not `nil`--it is a closure 749 | that returns a value. The value it returns, which is accessed via the 750 | `evaluate()` method, may be `nil`. If that value is `nil`, the `beNil` 751 | matcher function returns `true`, indicating that the expectation passed. 752 | 753 | ## Type Checking via Swift Generics 754 | 755 | Using Swift's generics, matchers can constrain the type of the actual value 756 | passed to the `expect` function by modifying the return type. 757 | 758 | For example, the following matcher, `haveDescription`, only accepts actual 759 | values that implement the `Printable` protocol. It checks their `description` 760 | against the one provided to the matcher function, and passes if they are the same: 761 | 762 | ```swift 763 | // Swift 764 | 765 | public func haveDescription(description: String) -> MatcherFunc { 766 | return MatcherFunc { actual, failureMessage in 767 | return actual.evaluate().description == description 768 | } 769 | } 770 | ``` 771 | 772 | ## Customizing Failure Messages 773 | 774 | By default, Nimble outputs the following failure message when an 775 | expectation fails: 776 | 777 | ``` 778 | expected to match, got <\(actual)> 779 | ``` 780 | 781 | You can customize this message by modifying the `failureMessage` struct 782 | from within your `MatcherFunc` closure. To change the verb "match" to 783 | something else, update the `postfixMessage` property: 784 | 785 | ```swift 786 | // Swift 787 | 788 | // Outputs: expected to be under the sea, got <\(actual)> 789 | failureMessage.postfixMessage = "be under the sea" 790 | ``` 791 | 792 | You can change how the `actual` value is displayed by updating 793 | `failureMessage.actualValue`. Or, to remove it altogether, set it to 794 | `nil`: 795 | 796 | ```swift 797 | // Swift 798 | 799 | // Outputs: expected to be under the sea 800 | failureMessage.actualValue = nil 801 | failureMessage.postfixMessage = "be under the sea" 802 | ``` 803 | 804 | ## Supporting Objective-C 805 | 806 | To use a custom matcher written in Swift from Objective-C, you'll have 807 | to extend the `NMBObjCMatcher` class, adding a new class method for your 808 | custom matcher. The example below defines the class method 809 | `+[NMBObjCMatcher beNilMatcher]`: 810 | 811 | ```swift 812 | // Swift 813 | 814 | extension NMBObjCMatcher { 815 | public class func beNilMatcher() -> NMBObjCMatcher { 816 | return NMBObjCMatcher { actualBlock, failureMessage, location in 817 | let block = ({ actualBlock() as NSObject? }) 818 | let expr = Expression(expression: block, location: location) 819 | return beNil().matches(expr, failureMessage: failureMessage) 820 | } 821 | } 822 | } 823 | ``` 824 | 825 | The above allows you to use the matcher from Objective-C: 826 | 827 | ```objc 828 | // Objective-C 829 | 830 | expect(actual).to([NMBObjCMatcher beNilMatcher]()); 831 | ``` 832 | 833 | To make the syntax easier to use, define a C function that calls the 834 | class method: 835 | 836 | ```objc 837 | // Objective-C 838 | 839 | FOUNDATION_EXPORT id beNil() { 840 | return [NMBObjCMatcher beNilMatcher]; 841 | } 842 | ``` 843 | 844 | ### Properly Handling `nil` in Objective-C Matchers 845 | 846 | When supporting Objective-C, make sure you handle `nil` appropriately. 847 | Like [Cedar](https://github.com/pivotal/cedar/issues/100), 848 | **most matchers do not match with nil**. This is to bring prevent test 849 | writers from being surprised by `nil` values where they did not expect 850 | them. 851 | 852 | Nimble provides the `beNil` matcher function for test writer that want 853 | to make expectations on `nil` objects: 854 | 855 | ```objc 856 | // Objective-C 857 | 858 | expect(nil).to(equal(nil)); // fails 859 | expect(nil).to(beNil()); // passes 860 | ``` 861 | 862 | # Installing Nimble 863 | 864 | > Nimble can be used on its own, or in conjunction with its sister 865 | project, [Quick](https://github.com/Quick/Quick). To install both 866 | Quick and Nimble, follow [the installation instructions in the Quick 867 | README](https://github.com/Quick/Quick#how-to-install-quick). 868 | 869 | To use Nimble to test your iOS or OS X applications, follow these 4 easy 870 | steps: 871 | 872 | 1. Clone the Nimble repository 873 | 2. Add Nimble.xcodeproj to your test target 874 | 3. Link Nimble.framework to your test target 875 | 4. Start writing expectations! 876 | 877 | For more detailed instructions on each of these steps, 878 | read [How to Install Quick](https://github.com/Quick/Quick#how-to-install-quick). 879 | Ignore the steps involving adding Quick to your project in order to 880 | install just Nimble. 881 | 882 | --------------------------------------------------------------------------------