init iOS module

This commit is contained in:
pengfei.zhou
2019-07-25 19:26:33 +08:00
parent 40416ff3fd
commit f86e7623a2
211 changed files with 20246 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
#import <Foundation/Foundation.h>
@class Configuration;
/**
Subclass QuickConfiguration and override the +[QuickConfiguration configure:]
method in order to configure how Quick behaves when running specs, or to define
shared examples that are used across spec files.
*/
@interface QuickConfiguration : NSObject
/**
This method is executed on each subclass of this class before Quick runs
any examples. You may override this method on as many subclasses as you like, but
there is no guarantee as to the order in which these methods are executed.
You can override this method in order to:
1. Configure how Quick behaves, by modifying properties on the Configuration object.
Setting the same properties in several methods has undefined behavior.
2. Define shared examples using `sharedExamples`.
@param configuration A mutable object that is used to configure how Quick behaves on
a framework level. For details on all the options, see the
documentation in Configuration.swift.
*/
+ (void)configure:(Configuration *)configuration;
@end

View File

@@ -0,0 +1,83 @@
#import "QuickConfiguration.h"
#import "World.h"
#import <objc/runtime.h>
typedef void (^QCKClassEnumerationBlock)(Class klass);
/**
Finds all direct subclasses of the given class and passes them to the block provided.
The classes are iterated over in the order that objc_getClassList returns them.
@param klass The base class to find subclasses of.
@param block A block that takes a Class. This block will be executed once for each subclass of klass.
*/
void qck_enumerateSubclasses(Class klass, QCKClassEnumerationBlock block) {
Class *classes = NULL;
int classesCount = objc_getClassList(NULL, 0);
if (classesCount > 0) {
classes = (Class *)calloc(sizeof(Class), classesCount);
classesCount = objc_getClassList(classes, classesCount);
Class subclass, superclass;
for(int i = 0; i < classesCount; i++) {
subclass = classes[i];
superclass = class_getSuperclass(subclass);
if (superclass == klass && block) {
block(subclass);
}
}
free(classes);
}
}
@implementation QuickConfiguration
#pragma mark - Object Lifecycle
/**
QuickConfiguration is not meant to be instantiated; it merely provides a hook
for users to configure how Quick behaves. Raise an exception if an instance of
QuickConfiguration is created.
*/
- (instancetype)init {
NSString *className = NSStringFromClass([self class]);
NSString *selectorName = NSStringFromSelector(@selector(configure:));
[NSException raise:NSInternalInconsistencyException
format:@"%@ is not meant to be instantiated; "
@"subclass %@ and override %@ to configure Quick.",
className, className, selectorName];
return nil;
}
#pragma mark - NSObject Overrides
/**
Hook into when QuickConfiguration is initialized in the runtime in order to
call +[QuickConfiguration configure:] on each of its subclasses.
*/
+ (void)initialize {
// Only enumerate over the subclasses of QuickConfiguration, not any of its subclasses.
if ([self class] == [QuickConfiguration class]) {
// Only enumerate over subclasses once, even if +[QuickConfiguration initialize]
// were to be called several times. This is necessary because +[QuickSpec initialize]
// manually calls +[QuickConfiguration initialize].
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
qck_enumerateSubclasses([QuickConfiguration class], ^(__unsafe_unretained Class klass) {
[[World sharedWorld] configure:^(Configuration *configuration) {
[klass configure:configuration];
}];
});
[[World sharedWorld] finalizeConfiguration];
});
}
}
#pragma mark - Public Interface
+ (void)configure:(Configuration *)configuration { }
@end

View File

@@ -0,0 +1,234 @@
#import <Foundation/Foundation.h>
@class ExampleMetadata;
/**
Provides a hook for Quick to be configured before any examples are run.
Within this scope, override the +[QuickConfiguration configure:] method
to set properties on a configuration object to customize Quick behavior.
For details, see the documentation for Configuraiton.swift.
@param name The name of the configuration class. Like any Objective-C
class name, this must be unique to the current runtime
environment.
*/
#define QuickConfigurationBegin(name) \
@interface name : QuickConfiguration; @end \
@implementation name \
/**
Marks the end of a Quick configuration.
Make sure you put this after `QuickConfigurationBegin`.
*/
#define QuickConfigurationEnd \
@end \
/**
Defines a new QuickSpec. Define examples and example groups within the space
between this and `QuickSpecEnd`.
@param name The name of the spec class. Like any Objective-C class name, this
must be unique to the current runtime environment.
*/
#define QuickSpecBegin(name) \
@interface name : QuickSpec; @end \
@implementation name \
- (void)spec { \
/**
Marks the end of a QuickSpec. Make sure you put this after `QuickSpecBegin`.
*/
#define QuickSpecEnd \
} \
@end \
typedef NSDictionary *(^QCKDSLSharedExampleContext)(void);
typedef void (^QCKDSLSharedExampleBlock)(QCKDSLSharedExampleContext);
typedef void (^QCKDSLEmptyBlock)(void);
typedef void (^QCKDSLExampleMetadataBlock)(ExampleMetadata *exampleMetadata);
#define QUICK_EXPORT FOUNDATION_EXPORT
QUICK_EXPORT void qck_beforeSuite(QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_afterSuite(QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_sharedExamples(NSString *name, QCKDSLSharedExampleBlock closure);
QUICK_EXPORT void qck_describe(NSString *description, QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_context(NSString *description, QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_beforeEach(QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_beforeEachWithMetadata(QCKDSLExampleMetadataBlock closure);
QUICK_EXPORT void qck_afterEach(QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_afterEachWithMetadata(QCKDSLExampleMetadataBlock closure);
QUICK_EXPORT void qck_pending(NSString *description, QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_xdescribe(NSString *description, QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_xcontext(NSString *description, QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_fdescribe(NSString *description, QCKDSLEmptyBlock closure);
QUICK_EXPORT void qck_fcontext(NSString *description, QCKDSLEmptyBlock closure);
#ifndef QUICK_DISABLE_SHORT_SYNTAX
/**
Defines a closure to be run prior to any examples in the test suite.
You may define an unlimited number of these closures, but there is no
guarantee as to the order in which they're run.
If the test suite crashes before the first example is run, this closure
will not be executed.
@param closure The closure to be run prior to any examples in the test suite.
*/
static inline void beforeSuite(QCKDSLEmptyBlock closure) {
qck_beforeSuite(closure);
}
/**
Defines a closure to be run after all of the examples in the test suite.
You may define an unlimited number of these closures, but there is no
guarantee as to the order in which they're run.
If the test suite crashes before all examples are run, this closure
will not be executed.
@param closure The closure to be run after all of the examples in the test suite.
*/
static inline void afterSuite(QCKDSLEmptyBlock closure) {
qck_afterSuite(closure);
}
/**
Defines a group of shared examples. These examples can be re-used in several locations
by using the `itBehavesLike` function.
@param name The name of the shared example group. This must be unique across all shared example
groups defined in a test suite.
@param closure A closure containing the examples. This behaves just like an example group defined
using `describe` or `context`--the closure may contain any number of `beforeEach`
and `afterEach` closures, as well as any number of examples (defined using `it`).
*/
static inline void sharedExamples(NSString *name, QCKDSLSharedExampleBlock closure) {
qck_sharedExamples(name, closure);
}
/**
Defines an example group. Example groups are logical groupings of examples.
Example groups can share setup and teardown code.
@param description An arbitrary string describing the example group.
@param closure A closure that can contain other examples.
*/
static inline void describe(NSString *description, QCKDSLEmptyBlock closure) {
qck_describe(description, closure);
}
/**
Defines an example group. Equivalent to `describe`.
*/
static inline void context(NSString *description, QCKDSLEmptyBlock closure) {
qck_context(description, closure);
}
/**
Defines a closure to be run prior to each example in the current example
group. This closure is not run for pending or otherwise disabled examples.
An example group may contain an unlimited number of beforeEach. They'll be
run in the order they're defined, but you shouldn't rely on that behavior.
@param closure The closure to be run prior to each example.
*/
static inline void beforeEach(QCKDSLEmptyBlock closure) {
qck_beforeEach(closure);
}
/**
Identical to QCKDSL.beforeEach, except the closure is provided with
metadata on the example that the closure is being run prior to.
*/
static inline void beforeEachWithMetadata(QCKDSLExampleMetadataBlock closure) {
qck_beforeEachWithMetadata(closure);
}
/**
Defines a closure to be run after each example in the current example
group. This closure is not run for pending or otherwise disabled examples.
An example group may contain an unlimited number of afterEach. They'll be
run in the order they're defined, but you shouldn't rely on that behavior.
@param closure The closure to be run after each example.
*/
static inline void afterEach(QCKDSLEmptyBlock closure) {
qck_afterEach(closure);
}
/**
Identical to QCKDSL.afterEach, except the closure is provided with
metadata on the example that the closure is being run after.
*/
static inline void afterEachWithMetadata(QCKDSLExampleMetadataBlock closure) {
qck_afterEachWithMetadata(closure);
}
/**
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.
@param description An arbitrary string describing the example or example group.
@param closure A closure that will not be evaluated.
*/
static inline void pending(NSString *description, QCKDSLEmptyBlock closure) {
qck_pending(description, closure);
}
/**
Use this to quickly mark a `describe` block as pending.
This disables all examples within the block.
*/
static inline void xdescribe(NSString *description, QCKDSLEmptyBlock closure) {
qck_xdescribe(description, closure);
}
/**
Use this to quickly mark a `context` block as pending.
This disables all examples within the block.
*/
static inline void xcontext(NSString *description, QCKDSLEmptyBlock closure) {
qck_xcontext(description, closure);
}
/**
Use this to quickly focus a `describe` block, focusing the examples in the block.
If any examples in the test suite are focused, only those examples are executed.
This trumps any explicitly focused or unfocused examples within the block--they are all treated as focused.
*/
static inline void fdescribe(NSString *description, QCKDSLEmptyBlock closure) {
qck_fdescribe(description, closure);
}
/**
Use this to quickly focus a `context` block. Equivalent to `fdescribe`.
*/
static inline void fcontext(NSString *description, QCKDSLEmptyBlock closure) {
qck_fcontext(description, closure);
}
#define it qck_it
#define xit qck_xit
#define fit qck_fit
#define itBehavesLike qck_itBehavesLike
#define xitBehavesLike qck_xitBehavesLike
#define fitBehavesLike qck_fitBehavesLike
#endif
#define qck_it qck_it_builder(@{}, @(__FILE__), __LINE__)
#define qck_xit qck_it_builder(@{Filter.pending: @YES}, @(__FILE__), __LINE__)
#define qck_fit qck_it_builder(@{Filter.focused: @YES}, @(__FILE__), __LINE__)
#define qck_itBehavesLike qck_itBehavesLike_builder(@{}, @(__FILE__), __LINE__)
#define qck_xitBehavesLike qck_itBehavesLike_builder(@{Filter.pending: @YES}, @(__FILE__), __LINE__)
#define qck_fitBehavesLike qck_itBehavesLike_builder(@{Filter.focused: @YES}, @(__FILE__), __LINE__)
typedef void (^QCKItBlock)(NSString *description, QCKDSLEmptyBlock closure);
typedef void (^QCKItBehavesLikeBlock)(NSString *description, QCKDSLSharedExampleContext context);
QUICK_EXPORT QCKItBlock qck_it_builder(NSDictionary *flags, NSString *file, NSUInteger line);
QUICK_EXPORT QCKItBehavesLikeBlock qck_itBehavesLike_builder(NSDictionary *flags, NSString *file, NSUInteger line);

View File

@@ -0,0 +1,79 @@
#import "QCKDSL.h"
#import "World.h"
#import "World+DSL.h"
void qck_beforeSuite(QCKDSLEmptyBlock closure) {
[[World sharedWorld] beforeSuite:closure];
}
void qck_afterSuite(QCKDSLEmptyBlock closure) {
[[World sharedWorld] afterSuite:closure];
}
void qck_sharedExamples(NSString *name, QCKDSLSharedExampleBlock closure) {
[[World sharedWorld] sharedExamples:name closure:closure];
}
void qck_describe(NSString *description, QCKDSLEmptyBlock closure) {
[[World sharedWorld] describe:description flags:@{} closure:closure];
}
void qck_context(NSString *description, QCKDSLEmptyBlock closure) {
qck_describe(description, closure);
}
void qck_beforeEach(QCKDSLEmptyBlock closure) {
[[World sharedWorld] beforeEach:closure];
}
void qck_beforeEachWithMetadata(QCKDSLExampleMetadataBlock closure) {
[[World sharedWorld] beforeEachWithMetadata:closure];
}
void qck_afterEach(QCKDSLEmptyBlock closure) {
[[World sharedWorld] afterEach:closure];
}
void qck_afterEachWithMetadata(QCKDSLExampleMetadataBlock closure) {
[[World sharedWorld] afterEachWithMetadata:closure];
}
QCKItBlock qck_it_builder(NSDictionary *flags, NSString *file, NSUInteger line) {
return ^(NSString *description, QCKDSLEmptyBlock closure) {
[[World sharedWorld] itWithDescription:description
flags:flags
file:file
line:line
closure:closure];
};
}
QCKItBehavesLikeBlock qck_itBehavesLike_builder(NSDictionary *flags, NSString *file, NSUInteger line) {
return ^(NSString *name, QCKDSLSharedExampleContext context) {
[[World sharedWorld] itBehavesLikeSharedExampleNamed:name
sharedExampleContext:context
flags:flags
file:file
line:line];
};
}
void qck_pending(NSString *description, QCKDSLEmptyBlock closure) {
[[World sharedWorld] pending:description closure:closure];
}
void qck_xdescribe(NSString *description, QCKDSLEmptyBlock closure) {
[[World sharedWorld] xdescribe:description flags:@{} closure:closure];
}
void qck_xcontext(NSString *description, QCKDSLEmptyBlock closure) {
qck_xdescribe(description, closure);
}
void qck_fdescribe(NSString *description, QCKDSLEmptyBlock closure) {
[[World sharedWorld] fdescribe:description flags:@{} closure:closure];
}
void qck_fcontext(NSString *description, QCKDSLEmptyBlock closure) {
qck_fdescribe(description, closure);
}

View File

@@ -0,0 +1,20 @@
#import <Quick/Quick-Swift.h>
@interface World (SWIFT_EXTENSION(Quick))
- (void)beforeSuite:(void (^ __nonnull)(void))closure;
- (void)afterSuite:(void (^ __nonnull)(void))closure;
- (void)sharedExamples:(NSString * __nonnull)name closure:(void (^ __nonnull)(NSDictionary * __nonnull (^ __nonnull)(void)))closure;
- (void)describe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure;
- (void)context:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure;
- (void)fdescribe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure;
- (void)xdescribe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure;
- (void)beforeEach:(void (^ __nonnull)(void))closure;
- (void)beforeEachWithMetadata:(void (^ __nonnull)(ExampleMetadata * __nonnull))closure;
- (void)afterEach:(void (^ __nonnull)(void))closure;
- (void)afterEachWithMetadata:(void (^ __nonnull)(ExampleMetadata * __nonnull))closure;
- (void)itWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure;
- (void)fitWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure;
- (void)xitWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure;
- (void)itBehavesLikeSharedExampleNamed:(NSString * __nonnull)name sharedExampleContext:(NSDictionary * __nonnull (^ __nonnull)(void))sharedExampleContext flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line;
- (void)pending:(NSString * __nonnull)description closure:(void (^ __nonnull)(void))closure;
@end

View File

@@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
//! Project version number for Quick.
FOUNDATION_EXPORT double QuickVersionNumber;
//! Project version string for Quick.
FOUNDATION_EXPORT const unsigned char QuickVersionString[];
#import "QuickSpec.h"
#import "QCKDSL.h"
#import "QuickConfiguration.h"

View File

@@ -0,0 +1,50 @@
#import <XCTest/XCTest.h>
/**
QuickSpec is a base class all specs written in Quick inherit from.
They need to inherit from QuickSpec, a subclass of XCTestCase, in
order to be discovered by the XCTest framework.
XCTest automatically compiles a list of XCTestCase subclasses included
in the test target. It iterates over each class in that list, and creates
a new instance of that class for each test method. It then creates an
"invocation" to execute that test method. The invocation is an instance of
NSInvocation, which represents a single message send in Objective-C.
The invocation is set on the XCTestCase instance, and the test is run.
Most of the code in QuickSpec is dedicated to hooking into XCTest events.
First, when the spec is first loaded and before it is sent any messages,
the +[NSObject initialize] method is called. QuickSpec overrides this method
to call +[QuickSpec spec]. This builds the example group stacks and
registers them with Quick.World, a global register of examples.
Then, XCTest queries QuickSpec for a list of test methods. Normally, XCTest
automatically finds all methods whose selectors begin with the string "test".
However, QuickSpec overrides this default behavior by implementing the
+[XCTestCase testInvocations] method. This method iterates over each example
registered in Quick.World, defines a new method for that example, and
returns an invocation to call that method to XCTest. Those invocations are
the tests that are run by XCTest. Their selector names are displayed in
the Xcode test navigation bar.
*/
@interface QuickSpec : XCTestCase
/**
Override this method in your spec to define a set of example groups
and examples.
@code
override func spec() {
describe("winter") {
it("is coming") {
// ...
}
}
}
@endcode
See DSL.swift for more information on what syntax is available.
*/
- (void)spec;
@end

View File

@@ -0,0 +1,141 @@
#import "QuickSpec.h"
#import "QuickConfiguration.h"
#import "World.h"
#import <Quick/Quick-Swift.h>
static QuickSpec *currentSpec = nil;
@interface QuickSpec ()
@property (nonatomic, strong) Example *example;
@end
@implementation QuickSpec
#pragma mark - XCTestCase Overrides
/**
The runtime sends initialize to each class in a program just before the class, or any class
that inherits from it, is sent its first message from within the program. QuickSpec hooks into
this event to compile the example groups for this spec subclass.
If an exception occurs when compiling the examples, report it to the user. Chances are they
included an expectation outside of a "it", "describe", or "context" block.
*/
+ (void)initialize {
[QuickConfiguration initialize];
World *world = [World sharedWorld];
[world performWithCurrentExampleGroup:[world rootExampleGroupForSpecClass:self] closure:^{
QuickSpec *spec = [self new];
@try {
[spec spec];
}
@catch (NSException *exception) {
[NSException raise:NSInternalInconsistencyException
format:@"An exception occurred when building Quick's example groups.\n"
@"Some possible reasons this might happen include:\n\n"
@"- An 'expect(...).to' expectation was evaluated outside of "
@"an 'it', 'context', or 'describe' block\n"
@"- 'sharedExamples' was called twice with the same name\n"
@"- 'itBehavesLike' was called with a name that is not registered as a shared example\n\n"
@"Here's the original exception: '%@', reason: '%@', userInfo: '%@'",
exception.name, exception.reason, exception.userInfo];
}
[self testInvocations];
}];
}
/**
Invocations for each test method in the test case. QuickSpec overrides this method to define a
new method for each example defined in +[QuickSpec spec].
@return An array of invocations that execute the newly defined example methods.
*/
+ (NSArray *)testInvocations {
NSArray *examples = [[World sharedWorld] examplesForSpecClass:[self class]];
NSMutableArray *invocations = [NSMutableArray arrayWithCapacity:[examples count]];
NSMutableSet<NSString*> *selectorNames = [NSMutableSet set];
for (Example *example in examples) {
SEL selector = [self addInstanceMethodForExample:example classSelectorNames:selectorNames];
NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.selector = selector;
[invocations addObject:invocation];
}
return invocations;
}
#pragma mark - Public Interface
- (void)spec { }
#pragma mark - Internal Methods
/**
QuickSpec uses this method to dynamically define a new instance method for the
given example. The instance method runs the example, catching any exceptions.
The exceptions are then reported as test failures.
In order to report the correct file and line number, examples must raise exceptions
containing following keys in their userInfo:
- "SenTestFilenameKey": A String representing the file name
- "SenTestLineNumberKey": An Int representing the line number
These keys used to be used by SenTestingKit, and are still used by some testing tools
in the wild. See: https://github.com/Quick/Quick/pull/41
@return The selector of the newly defined instance method.
*/
+ (SEL)addInstanceMethodForExample:(Example *)example classSelectorNames:(NSMutableSet<NSString*> *)selectorNames {
IMP implementation = imp_implementationWithBlock(^(QuickSpec *self){
self.example = example;
currentSpec = self;
[example run];
});
const char *types = [[NSString stringWithFormat:@"%s%s%s", @encode(void), @encode(id), @encode(SEL)] UTF8String];
NSString *originalName = example.name.qck_c99ExtendedIdentifier;
NSString *selectorName = originalName;
NSUInteger i = 2;
while ([selectorNames containsObject:selectorName]) {
selectorName = [NSString stringWithFormat:@"%@_%tu", originalName, i++];
}
[selectorNames addObject:selectorName];
SEL selector = NSSelectorFromString(selectorName);
class_addMethod(self, selector, implementation, types);
return selector;
}
/**
This method is used to record failures, whether they represent example
expectations that were not met, or exceptions raised during test setup
and teardown. By default, the failure will be reported as an
XCTest failure, and the example will be highlighted in Xcode.
*/
- (void)recordFailureWithDescription:(NSString *)description
inFile:(NSString *)filePath
atLine:(NSUInteger)lineNumber
expected:(BOOL)expected {
if (self.example.isSharedExample) {
filePath = self.example.callsite.file;
lineNumber = self.example.callsite.line;
}
[currentSpec.testRun recordFailureWithDescription:description
inFile:filePath
atLine:lineNumber
expected:expected];
}
@end

View File

@@ -0,0 +1,18 @@
#import <Quick/Quick-Swift.h>
@class ExampleGroup;
@class ExampleMetadata;
SWIFT_CLASS("_TtC5Quick5World")
@interface World
@property (nonatomic) ExampleGroup * __nullable currentExampleGroup;
@property (nonatomic) ExampleMetadata * __nullable currentExampleMetadata;
@property (nonatomic) BOOL isRunningAdditionalSuites;
+ (World * __nonnull)sharedWorld;
- (void)configure:(void (^ __nonnull)(Configuration * __nonnull))closure;
- (void)finalizeConfiguration;
- (ExampleGroup * __nonnull)rootExampleGroupForSpecClass:(Class __nonnull)cls;
- (NSArray * __nonnull)examplesForSpecClass:(Class __nonnull)specClass;
- (void)performWithCurrentExampleGroup:(ExampleGroup * __nonnull)group closure:(void (^ __nonnull)(void))closure;
@end

View File

@@ -0,0 +1,40 @@
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import <Quick/Quick-Swift.h>
@interface XCTestSuite (QuickTestSuiteBuilder)
@end
@implementation XCTestSuite (QuickTestSuiteBuilder)
/**
In order to ensure we can correctly build dynamic test suites, we need to
replace some of the default test suite constructors.
*/
+ (void)load {
Method testCaseWithName = class_getClassMethod(self, @selector(testSuiteForTestCaseWithName:));
Method hooked_testCaseWithName = class_getClassMethod(self, @selector(qck_hooked_testSuiteForTestCaseWithName:));
method_exchangeImplementations(testCaseWithName, hooked_testCaseWithName);
}
/**
The `+testSuiteForTestCaseWithName:` method is called when a specific test case
class is run from the Xcode test navigator. If the built test suite is `nil`,
Xcode will not run any tests for that test case.
Given if the following test case class is run from the Xcode test navigator:
FooSpec
testFoo
testBar
XCTest will invoke this once per test case, with test case names following this format:
FooSpec/testFoo
FooSpec/testBar
*/
+ (nullable instancetype)qck_hooked_testSuiteForTestCaseWithName:(nonnull NSString *)name {
return [QuickTestSuite selectedTestSuiteForTestCaseWithName:name];
}
@end