Skip to content

Commit

Permalink
WIP: Test global hooks with a bit more rigor
Browse files Browse the repository at this point in the history
In particular, use TestObservationHelper to count the number of started
and finished SPECS so we can compare this to the number of times the
+beforeEach and +afterEach methods are called on classes conforming to
CDRHooks

Unfortunately this isn't universal as the observer only works in test
bundle builds.
  • Loading branch information
Sam Coward and Tim Jarratt committed Apr 13, 2016
1 parent 82debd2 commit 2db594b
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 14 deletions.
5 changes: 4 additions & 1 deletion Source/Headers/Project/XCTest/CDRXCTestSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

// This file redeclares various XCTest classes and selectors to make the compiler happy.

@class XCTestSuite;
@class XCTestSuite, XCTestCase;
@protocol XCTestObservation
@optional
- (void)testSuiteWillStart:(XCTestSuite *)testSuite;
- (void)testSuiteDidFinish:(XCTestSuite *)testSuite;
- (void)testCaseWillStart:(XCTestCase *)testCase;
- (void)testCaseDidFinish:(XCTestCase *)testCase;
@end


Expand Down
31 changes: 23 additions & 8 deletions Spec/CDRHooksSpec.mm
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#import "Cedar.h"
#import "TestObservationHelper.h"

static BOOL conformantClassBeforeEachWasTriggered__ = NO;
static NSUInteger conformantClassBeforeEachTriggeredCount__ = 0;
static BOOL nonConformantClassBeforeEachWasTriggered__ = NO;
static BOOL conformantClassAfterEachWasTriggered__ = NO;
static NSUInteger conformantClassAfterEachTriggeredCount__ = 0;
static BOOL nonConformantClassAfterEachWasTriggered__ = NO;

using namespace Cedar::Matchers;
Expand All @@ -19,11 +20,11 @@ @interface ClassThatConformsToCDRHooks ()<CDRHooks>

@implementation ClassThatConformsToCDRHooks
+ (void)beforeEach {
conformantClassBeforeEachWasTriggered__ = YES;
++ conformantClassBeforeEachTriggeredCount__;
}

+ (void)afterEach {
conformantClassAfterEachWasTriggered__ = YES;
++ conformantClassAfterEachTriggeredCount__;
}
@end

Expand All @@ -37,23 +38,37 @@ + (void)afterEach {
}
@end

void verifyAfterEachCalledAsExpected() {
NSCAssert(conformantClassAfterEachWasTriggered__, @"Expected +[ClassThatConformsToCDRHooks afterEach] to be called, but it wasn't. (From %s)", __FILE__);
void verifyBeforeEachCalledAsExpectedForAllSpecs() {
NSCAssert(conformantClassBeforeEachTriggeredCount__ == [TestObservationHelper startedSpecCount], @"Expected +[ClassThatConformsToCDRHooks beforeEach] to be called %@ times, but it was called %@ times. (From %s)", @([TestObservationHelper finishedSpecCount]), @(conformantClassAfterEachTriggeredCount__), __FILE__);
NSCAssert(!nonConformantClassBeforeEachWasTriggered__, @"Expected +[ClassThatDoesntConformToCDRHooks beforeEach] to NOT be called, but it was. (From %s)", __FILE__);
}

void verifyAfterEachCalledAsExpectedForAllSpecs() {
NSCAssert(conformantClassAfterEachTriggeredCount__ == [TestObservationHelper finishedSpecCount], @"Expected +[ClassThatConformsToCDRHooks afterEach] to be called %@ times, but it was called %@ times. (From %s)", @([TestObservationHelper finishedSpecCount]), @(conformantClassAfterEachTriggeredCount__), __FILE__);
NSCAssert(!nonConformantClassAfterEachWasTriggered__, @"Expected +[ClassThatDoesntConformToCDRHooks afterEach] to NOT be called, but it was. (From %s)", __FILE__);
}

SPEC_BEGIN(CDRHooksSpec)

describe(@"global beforeEach", ^{
it(@"should run the +beforeEach before only for CDRHooks conformant classes", ^{
expect(conformantClassBeforeEachWasTriggered__).to(be_truthy);
expect(conformantClassBeforeEachTriggeredCount__).to(equal([TestObservationHelper startedSpecCount]));
expect(nonConformantClassBeforeEachWasTriggered__).to(be_falsy);
});

it(@"should have run before every single spec", ^{
atexit(verifyBeforeEachCalledAsExpectedForAllSpecs);
});
});

describe(@"global afterEach", ^{
it(@"should run the +afterEach before only for CDRHooks conformant classes", ^{
expect(conformantClassBeforeEachTriggeredCount__).to(equal([TestObservationHelper startedSpecCount]));
expect(nonConformantClassAfterEachWasTriggered__).to(be_falsy);
});

it(@"should run after all specs", ^{
atexit(verifyAfterEachCalledAsExpected);
atexit(verifyAfterEachCalledAsExpectedForAllSpecs);
});
});

Expand Down
2 changes: 2 additions & 0 deletions Spec/SpecBundle/Support/TestObservationHelper.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#import <Foundation/Foundation.h>

@interface TestObservationHelper : NSObject
+ (NSUInteger)startedSpecCount;
+ (NSUInteger)finishedSpecCount;
+ (NSArray *)knownTestSuites;
@end
43 changes: 38 additions & 5 deletions Spec/SpecBundle/Support/TestObservationHelper.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,64 @@ @interface XCTestSuite
+ (instancetype)defaultTestSuite;
@end

@interface TestObservationHelper () <XCTestObservation> @end
@interface TestObservationHelper () <XCTestObservation>
@property (nonatomic) BOOL currentTestsAreXCTestCases;
@end

// This class is loaded as the NSPrincipalClass of test bundles that require it, at which point
// it registers itself as a test observer.
@implementation TestObservationHelper

static NSMutableArray *_knownTestSuites;
static NSMutableArray *knownTestSuites__;
static NSUInteger finishedSpecCount__;
static NSUInteger startedSpecCount__;

- (instancetype)init {
if (self = [super init]) {
Class observationCenterClass = NSClassFromString(@"XCTestObservationCenter");
if (observationCenterClass && [observationCenterClass respondsToSelector:@selector(sharedTestObservationCenter)]) {
_knownTestSuites = [NSMutableArray array];
knownTestSuites__ = [NSMutableArray array];
finishedSpecCount__ = startedSpecCount__ = 0;
_currentTestsAreXCTestCases = NO;
[[observationCenterClass sharedTestObservationCenter] addTestObserver:self];
}
}
return self;
}

+ (NSArray *)knownTestSuites {
return [_knownTestSuites copy] ?: @[[XCTestSuite defaultTestSuite]];
return [knownTestSuites__ copy] ?: @[[XCTestSuite defaultTestSuite]];
}

+ (NSUInteger)startedSpecCount {
return startedSpecCount__;
}

+ (NSUInteger)finishedSpecCount {
return finishedSpecCount__;
}

- (void)testSuiteWillStart:(XCTestSuite *)suite {
[_knownTestSuites addObject:suite];
if ([(id)suite isKindOfClass:NSClassFromString(@"XCTestCaseSuite")]) {
_currentTestsAreXCTestCases = YES;
}
[knownTestSuites__ addObject:suite];
}

- (void)testSuiteDidFinish:(XCTestSuite *)suite {
_currentTestsAreXCTestCases = NO;
}

- (void)testCaseWillStart:(XCTestCase *)testCase {
if (!_currentTestsAreXCTestCases) {
++ startedSpecCount__;
}
}

- (void)testCaseDidFinish:(XCTestCase *)testCase {
if (!_currentTestsAreXCTestCases) {
++ finishedSpecCount__;
}
}

@end

0 comments on commit 2db594b

Please sign in to comment.