Skip to content

Commit

Permalink
Merge pull request Quick#701 from mosamer/master
Browse files Browse the repository at this point in the history
Introduce `Behavior<Context>`
  • Loading branch information
jeffh authored May 9, 2017
2 parents 1712d3a + ddbe9cf commit 813d9f5
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 2 deletions.
25 changes: 25 additions & 0 deletions Quick.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@
AED9C8641CC8A7BD00432F62 /* CrossReferencingSpecs.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED9C8621CC8A7BD00432F62 /* CrossReferencingSpecs.swift */; };
AED9C8651CC8A7BD00432F62 /* CrossReferencingSpecs.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED9C8621CC8A7BD00432F62 /* CrossReferencingSpecs.swift */; };
CD264DBD1DDA147A0038B0EB /* AfterSuiteTests+ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 64076D241D6D80B500E2B499 /* AfterSuiteTests+ObjC.m */; };
CE175D4E1E8D6B4900EB5E84 /* Behavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE175D4D1E8D6B4900EB5E84 /* Behavior.swift */; };
CE175D4F1E8D6B4900EB5E84 /* Behavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE175D4D1E8D6B4900EB5E84 /* Behavior.swift */; };
CE175D501E8D6B4900EB5E84 /* Behavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE175D4D1E8D6B4900EB5E84 /* Behavior.swift */; };
CE4A578A1EA5DC270063C0D4 /* BehaviorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4A57891EA5DC270063C0D4 /* BehaviorTests.swift */; };
CE4A578E1EA7251C0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4A578D1EA7251C0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift */; };
CE4A57911EA7252E0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4A578D1EA7251C0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift */; };
CE4A57921EA725300063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4A578D1EA7251C0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift */; };
CE4A57931EA725420063C0D4 /* BehaviorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4A57891EA5DC270063C0D4 /* BehaviorTests.swift */; };
CE4A57941EA725440063C0D4 /* BehaviorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4A57891EA5DC270063C0D4 /* BehaviorTests.swift */; };
CE57CEDD1C430BD200D63004 /* NSBundle+CurrentTestBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE57CED81C430BD200D63004 /* NSBundle+CurrentTestBundle.swift */; };
CE57CEDE1C430BD200D63004 /* QuickSelectedTestSuiteBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE57CED91C430BD200D63004 /* QuickSelectedTestSuiteBuilder.swift */; };
CE57CEDF1C430BD200D63004 /* QuickTestSuite.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE57CEDA1C430BD200D63004 /* QuickTestSuite.swift */; };
Expand Down Expand Up @@ -500,6 +509,9 @@
CD261AC81DEC8B0000A8863C /* QuickConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickConfiguration.swift; sourceTree = "<group>"; };
CD3451461E4703D4000C8633 /* QuickMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickMain.swift; sourceTree = "<group>"; };
CD3451471E4703D4000C8633 /* QuickSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickSpec.swift; sourceTree = "<group>"; };
CE175D4D1E8D6B4900EB5E84 /* Behavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Behavior.swift; sourceTree = "<group>"; };
CE4A57891EA5DC270063C0D4 /* BehaviorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BehaviorTests.swift; sourceTree = "<group>"; };
CE4A578D1EA7251C0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionalTests_BehaviorTests_Behaviors.swift; sourceTree = "<group>"; };
CE57CED81C430BD200D63004 /* NSBundle+CurrentTestBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSBundle+CurrentTestBundle.swift"; sourceTree = "<group>"; };
CE57CED91C430BD200D63004 /* QuickSelectedTestSuiteBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickSelectedTestSuiteBuilder.swift; sourceTree = "<group>"; };
CE57CEDA1C430BD200D63004 /* QuickTestSuite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickTestSuite.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -788,6 +800,7 @@
7B5358CA1C3D4E2A00A23FAA /* ContextTests.swift */,
AED9C8621CC8A7BD00432F62 /* CrossReferencingSpecs.swift */,
DED3037C1DF6CF140041394E /* BundleModuleNameTests.swift */,
CE4A57891EA5DC270063C0D4 /* BehaviorTests.swift */,
);
path = FunctionalTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -893,6 +906,7 @@
CE57CEDB1C430BD200D63004 /* URL+FileName.swift */,
34C586071C4AC5E500D4F057 /* ErrorUtility.swift */,
DAEB6B911943873100289F44 /* Supporting Files */,
CE175D4D1E8D6B4900EB5E84 /* Behavior.swift */,
);
name = Quick;
path = Sources/Quick;
Expand Down Expand Up @@ -932,6 +946,7 @@
isa = PBXGroup;
children = (
DA8F91AD19F32CE2006F6675 /* FunctionalTests_SharedExamplesTests_SharedExamples.swift */,
CE4A578D1EA7251C0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift */,
);
path = Fixtures;
sourceTree = "<group>";
Expand Down Expand Up @@ -1442,6 +1457,7 @@
1F118D001BDCA536005013A2 /* Closures.swift in Sources */,
1F118D051BDCA536005013A2 /* ExampleMetadata.swift in Sources */,
1F118D061BDCA536005013A2 /* ExampleGroup.swift in Sources */,
CE175D501E8D6B4900EB5E84 /* Behavior.swift in Sources */,
CE590E1F1C431FE400253D19 /* QuickTestSuite.swift in Sources */,
1F118D091BDCA536005013A2 /* QuickSpec.m in Sources */,
1F118D011BDCA536005013A2 /* ExampleHooks.swift in Sources */,
Expand All @@ -1457,7 +1473,9 @@
1F118D1C1BDCA556005013A2 /* BeforeSuiteTests.swift in Sources */,
1F118D1D1BDCA556005013A2 /* BeforeSuiteTests+ObjC.m in Sources */,
1F118D0E1BDCA547005013A2 /* QCKSpecRunner.m in Sources */,
CE4A57931EA725420063C0D4 /* BehaviorTests.swift in Sources */,
1F118D141BDCA556005013A2 /* FailureTests+ObjC.m in Sources */,
CE4A57911EA7252E0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift in Sources */,
DED3037F1DF6CF140041394E /* BundleModuleNameTests.swift in Sources */,
1F118D0F1BDCA54B005013A2 /* FunctionalTests_SharedExamplesTests_SharedExamples.swift in Sources */,
1F118D101BDCA556005013A2 /* Configuration+AfterEach.swift in Sources */,
Expand Down Expand Up @@ -1521,6 +1539,7 @@
DA3124ED19FCAEE8002858A7 /* World+DSL.swift in Sources */,
DA408BE519FF5599005DF92A /* ExampleHooks.swift in Sources */,
34F375AC19515CA700CE1B99 /* Example.swift in Sources */,
CE175D4F1E8D6B4900EB5E84 /* Behavior.swift in Sources */,
CE590E1A1C431FE300253D19 /* QuickTestSuite.swift in Sources */,
DA3124E719FCAEE8002858A7 /* DSL.swift in Sources */,
DA6B30191A4DB0D500FFB148 /* Filter.swift in Sources */,
Expand All @@ -1542,10 +1561,12 @@
DAB0137019FC4315006AFBEE /* SharedExamples+BeforeEachTests.swift in Sources */,
DA8F91A619F3208B006F6675 /* BeforeSuiteTests.swift in Sources */,
DA8C00221A01E4B900CE58A6 /* QuickConfigurationTests.m in Sources */,
CE4A578A1EA5DC270063C0D4 /* BehaviorTests.swift in Sources */,
DAA63EA419F7637300CD0A3B /* PendingTests.swift in Sources */,
DA8F91AC19F3299E006F6675 /* SharedExamplesTests.swift in Sources */,
DA7AE6F219FC493F000AFDCE /* ItTests.swift in Sources */,
4748E8951A6AEBB3009EC992 /* SharedExamples+BeforeEachTests+ObjC.m in Sources */,
CE4A578E1EA7251C0063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift in Sources */,
DA8F91AF19F32CE2006F6675 /* FunctionalTests_SharedExamplesTests_SharedExamples.swift in Sources */,
DAE714FB19FF682A005905B8 /* Configuration+AfterEachTests.swift in Sources */,
AE4E58151C73097C00420A2E /* XCTestObservationCenter+QCKSuspendObservation.m in Sources */,
Expand Down Expand Up @@ -1639,6 +1660,7 @@
7B44ADBE1C5444940007AF2E /* HooksPhase.swift in Sources */,
DA3124EC19FCAEE8002858A7 /* World+DSL.swift in Sources */,
DA408BE419FF5599005DF92A /* ExampleHooks.swift in Sources */,
CE175D4E1E8D6B4900EB5E84 /* Behavior.swift in Sources */,
34F375AB19515CA700CE1B99 /* Example.swift in Sources */,
DA3124E619FCAEE8002858A7 /* DSL.swift in Sources */,
DA6B30181A4DB0D500FFB148 /* Filter.swift in Sources */,
Expand All @@ -1654,7 +1676,9 @@
DA8F919919F31680006F6675 /* QCKSpecRunner.m in Sources */,
DA8940F01B35B1FA00161061 /* FailureUsingXCTAssertTests+ObjC.m in Sources */,
4728253B1A5EECCE008DC74F /* SharedExamplesTests+ObjC.m in Sources */,
CE4A57941EA725440063C0D4 /* BehaviorTests.swift in Sources */,
DAE714F019FF65D3005905B8 /* Configuration+BeforeEachTests.swift in Sources */,
CE4A57921EA725300063C0D4 /* FunctionalTests_BehaviorTests_Behaviors.swift in Sources */,
DED3037D1DF6CF140041394E /* BundleModuleNameTests.swift in Sources */,
DA05D61019F73A3800771050 /* AfterEachTests.swift in Sources */,
DAB0136F19FC4315006AFBEE /* SharedExamples+BeforeEachTests.swift in Sources */,
Expand Down Expand Up @@ -2609,6 +2633,7 @@
732D8D6C1E516780008558BD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DA5663F31A4C8D8500193C88 /* Build configuration list for PBXNativeTarget "QuickFocused - macOSTests" */ = {
isa = XCConfigurationList;
Expand Down
17 changes: 17 additions & 0 deletions Sources/Quick/Behavior.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
A `Behavior` encapsulates a set of examples that can be re-used in several locations using the `itBehavesLike` function with a context instance of the generic type.
*/

open class Behavior<Context> {

open static var name: String { return String(describing: self) }
/**
override this method in your behavior to define a set of reusable examples.

This behaves just like an example group defines using `describe` or `context`--it may contain any number of `beforeEach`
and `afterEach` closures, as well as any number of examples (defined using `it`).

- parameter aContext: A closure that, when evaluated, returns a `Context` instance that provide the information on the subject.
*/
open class func spec(_ aContext: @escaping () -> Context) {}
}
30 changes: 30 additions & 0 deletions Sources/Quick/DSL/DSL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ public func itBehavesLike(_ name: String, flags: FilterFlags = [:], file: String
World.sharedWorld.itBehavesLike(name, sharedExampleContext: sharedExampleContext, flags: flags, file: file, line: line)
}

/**
Inserts the examples defined using a `Behavior` into the current example group.
The shared examples are executed at this location, as if they were written out manually.
This function also passes a strongly-typed context that can be evaluated to give the shared examples extra information on the subject of the example.

- parameter behavior: The type of `Behavior` class defining the example group to be executed.
- parameter context: A closure that, when evaluated, returns an instance of `Behavior`'s context type to provide its example group with extra information on the subject of the example.
- parameter flags: A mapping of string keys to booleans that can be used to filter examples or example groups.
Empty by default.
- parameter file: The absolute path to the file containing the current example group. A sensible default is provided.
- parameter line: The line containing the current example group. A sensible default is provided.
*/
public func itBehavesLike<C>(_ behavior: Behavior<C>.Type, flags: FilterFlags = [:], file: String = #file, line: UInt = #line, context: @escaping () -> C) {
World.sharedWorld.itBehavesLike(behavior, context: context, flags: flags, file: file, line: line)
}

/**
Defines an example or example group that should not be executed. Use `pending` to temporarily disable
examples or groups that should not be run yet.
Expand Down Expand Up @@ -202,6 +218,13 @@ public func xit(_ description: String, flags: FilterFlags = [:], file: String =
World.sharedWorld.xit(description, flags: flags, file: file, line: line, closure: closure)
}

/**
Use this to quicklu mark an `itBehavesLike` closure as pending.
This disables the example group defined by this behavior and ensures the code within is never run.
*/
public func xitBehavesLike<C>(_ behavior: Behavior<C>.Type, flags: FilterFlags = [:], file: String = #file, line: UInt = #line, context: @escaping () -> C) {
World.sharedWorld.xitBehavesLike(behavior, context: context, flags: flags, file: file, line: line)
}
/**
Use this to quickly focus a `describe` closure, focusing the examples in the closure.
If any examples in the test suite are focused, only those examples are executed.
Expand Down Expand Up @@ -239,3 +262,10 @@ public func fitBehavesLike(_ name: String, flags: FilterFlags = [:], file: Strin
public func fitBehavesLike(_ name: String, flags: FilterFlags = [:], file: String = #file, line: UInt = #line, sharedExampleContext: @escaping SharedExampleContext) {
World.sharedWorld.fitBehavesLike(name, sharedExampleContext: sharedExampleContext, flags: flags, file: file, line: line)
}

/**
Use this to quickly focus on `itBehavesLike` closure.
*/
public func fitBehavesLike<C>(_ behavior: Behavior<C>.Type, flags: FilterFlags = [:], file: String = #file, line: UInt = #line, context: @escaping () -> C) {
World.sharedWorld.fitBehavesLike(behavior, context: context, flags: flags, file: file, line: line)
}
30 changes: 30 additions & 0 deletions Sources/Quick/DSL/World+DSL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,36 @@ extension World {
self.itBehavesLike(name, sharedExampleContext: sharedExampleContext, flags: focusedFlags, file: file, line: line)
}

internal func itBehavesLike<C>(_ behavior: Behavior<C>.Type, context: @escaping () -> C, flags: FilterFlags, file: String, line: UInt) {
guard currentExampleMetadata == nil else {
raiseError("'itBehavesLike' cannot be used inside '\(currentPhase)', 'itBehavesLike' may only be used inside 'context' or 'describe'. ")
}
let callsite = Callsite(file: file, line: line)
let closure = behavior.spec
let group = ExampleGroup(description: behavior.name, flags: flags)
currentExampleGroup.appendExampleGroup(group)
performWithCurrentExampleGroup(group) {
closure(context)
}

group.walkDownExamples { (example: Example) in
example.isSharedExample = true
example.callsite = callsite
}
}

internal func fitBehavesLike<C>(_ behavior: Behavior<C>.Type, context: @escaping () -> C, flags: FilterFlags, file: String, line: UInt) {
var focusedFlags = flags
focusedFlags[Filter.focused] = true
self.itBehavesLike(behavior, context: context, flags: focusedFlags, file: file, line: line)
}

internal func xitBehavesLike<C>(_ behavior: Behavior<C>.Type, context: @escaping () -> C, flags: FilterFlags, file: String, line: UInt) {
var pendingFlags = flags
pendingFlags[Filter.pending] = true
self.itBehavesLike(behavior, context: context, flags: pendingFlags, file: file, line: line)
}

#if _runtime(_ObjC)
@objc(itWithDescription:flags:file:line:closure:)
private func objc_it(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
Expand Down
11 changes: 10 additions & 1 deletion Tests/QuickTests/QuickFocusedTests/FocusedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ class FunctionalTests_FocusedSpec_SharedExamplesConfiguration: QuickConfiguratio
}
}

class FunctionalTests_FocusedSpec_Behavior: Behavior<Void> {
override static func spec(_ aContext: @escaping () -> Void) {
it("pass once") { expect(true).to(beTruthy()) }
it("pass twice") { expect(true).to(beTruthy()) }
it("pass three times") { expect(true).to(beTruthy()) }
}
}

// The following `QuickSpec`s will be run in a same test suite with other specs
// on SwiftPM. We must avoid that the focused flags below affect other specs, so
// the examples of the two specs must be gathered lastly. That is the reason why
Expand All @@ -28,6 +36,7 @@ class _FunctionalTests_FocusedSpec_Focused: QuickSpec {
}

fitBehavesLike("two passing shared examples")
fitBehavesLike(FunctionalTests_FocusedSpec_Behavior.self) {_ in ()}
}
}

Expand All @@ -54,6 +63,6 @@ final class FocusedTests: XCTestCase, XCTestCaseProvider {
_FunctionalTests_FocusedSpec_Focused.self,
_FunctionalTests_FocusedSpec_Unfocused.self
])
XCTAssertEqual(result?.executionCount, 5 as UInt)
XCTAssertEqual(result?.executionCount, 8 as UInt)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation
import Quick
import Nimble

class FunctionalTests_BehaviorTests_Behavior: Behavior<String> {
override static func spec(_ aContext: @escaping () -> String) {
it("passed the correct parameters via the context") {
let callsite = aContext()
expect(callsite).to(equal("BehaviorSpec"))
}
}
}

class FunctionalTests_BehaviorTests_Behavior2: Behavior<Void> {
override static func spec(_ aContext: @escaping () -> Void) {
it("passes once") { expect(true).to(beTruthy()) }
it("passes twice") { expect(true).to(beTruthy()) }
it("passes three times") { expect(true).to(beTruthy()) }
}
}
59 changes: 59 additions & 0 deletions Tests/QuickTests/QuickTests/FunctionalTests/BehaviorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Foundation
import Quick
import Nimble
import XCTest

class FunctionalTests_BehaviorTests_Spec: QuickSpec {
override func spec() {
itBehavesLike(FunctionalTests_BehaviorTests_Behavior2.self) {_ in ()}
}
}

class FunctionalTests_BehaviorTests_ContextSpec: QuickSpec {
override func spec() {
itBehavesLike(FunctionalTests_BehaviorTests_Behavior.self) {
"BehaviorSpec"
}
}
}

#if _runtime(_ObjC) && !SWIFT_PACKAGE
class FunctionalTests_BehaviorTests_ErrorSpec: QuickSpec {
override func spec() {
describe("error handling when misusing ordering") {
it("should throw an exception when including itBehavesLike in it block") {
expect {
itBehavesLike(FunctionalTests_BehaviorTests_Behavior2.self) { () }
}
.to(raiseException {(exception: NSException) in
expect(exception.name).to(equal(NSExceptionName.internalInconsistencyException))
expect(exception.reason).to(equal("'itBehavesLike' cannot be used inside 'it', 'itBehavesLike' may only be used inside 'context' or 'describe'. "))
})
}
}
}
}
#endif

final class BehaviorTests: XCTestCase, XCTestCaseProvider {

static var allTests: [(String, (BehaviorTests) -> () throws -> Void)] {
return [
("testBehaviorPassContextToExamples",
testBehaviorPassContextToExamples),
("testBehaviorExecutesThreeExamples",
testBehaviorExecutesThreeExamples)
]
}

func testBehaviorExecutesThreeExamples() {
let result = qck_runSpec(FunctionalTests_BehaviorTests_Spec.self)
XCTAssert(result!.hasSucceeded)
XCTAssertEqual(result!.executionCount, 3 as UInt)
}

func testBehaviorPassContextToExamples() {
let result = qck_runSpec(FunctionalTests_BehaviorTests_ContextSpec.self)
XCTAssert(result!.hasSucceeded)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import Nimble
var oneExampleBeforeEachExecutedCount = 0
var onlyPendingExamplesBeforeEachExecutedCount = 0

class FunctionalTests_PendingSpec_Behavior: Behavior<Void> {
override static func spec(_ aContext: @escaping () -> Void) {
it("an example that will not run") {
expect(true).to(beFalsy())
}
}
}
class FunctionalTests_PendingSpec: QuickSpec {
override func spec() {
xit("an example that will not run") {
expect(true).to(beFalsy())
}

xitBehavesLike(FunctionalTests_PendingSpec_Behavior.self) {_ in }
describe("a describe block containing only one enabled example") {
beforeEach { oneExampleBeforeEachExecutedCount += 1 }
it("an example that will run") {}
Expand Down

0 comments on commit 813d9f5

Please sign in to comment.