forked from TextureGroup/Texture
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOCMockObject+ASAdditions.mm
237 lines (199 loc) · 7.05 KB
/
OCMockObject+ASAdditions.mm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
//
// OCMockObject+ASAdditions.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import "OCMockObject+ASAdditions.h"
#import <OCMock/OCMock.h>
#import <objc/runtime.h>
#import "ASTestCase.h"
#import <AsyncDisplayKit/ASAssert.h>
#import "debugbreak.h"
@interface ASTestCase (OCMockObjectRegistering)
- (void)registerMockObject:(id)mockObject;
@end
@implementation OCMockObject (ASAdditions)
+ (void)load
{
// [OCProtocolMockObject respondsToSelector:] <-> [(self) swizzled_protocolMockRespondsToSelector:]
Method orig = class_getInstanceMethod(OCMockObject.protocolMockObjectClass, @selector(respondsToSelector:));
Method newMethod = class_getInstanceMethod(self, @selector(swizzled_protocolMockRespondsToSelector:));
method_exchangeImplementations(orig, newMethod);
// init <-> swizzled_init
{
Method origInit = class_getInstanceMethod([OCMockObject class], @selector(init));
Method newInit = class_getInstanceMethod(self, @selector(swizzled_init));
method_exchangeImplementations(origInit, newInit);
}
// (class mock) description <-> swizzled_classMockDescription
{
Method orig = class_getInstanceMethod(OCMockObject.classMockObjectClass, @selector(description));
Method newMethod = class_getInstanceMethod(self, @selector(swizzled_classMockDescription));
method_exchangeImplementations(orig, newMethod);
}
}
/// Since OCProtocolMockObject is private, use this method to get the class.
+ (Class)protocolMockObjectClass
{
static Class c;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
c = NSClassFromString(@"OCProtocolMockObject");
NSAssert(c != Nil, nil);
});
return c;
}
/// Since OCClassMockObject is private, use this method to get the class.
+ (Class)classMockObjectClass
{
static Class c;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
c = NSClassFromString(@"OCClassMockObject");
NSAssert(c != Nil, nil);
});
return c;
}
/// Whether the user has opted-in to specify which optional methods are implemented for this object.
- (BOOL)hasSpecifiedOptionalProtocolMethods
{
return objc_getAssociatedObject(self, @selector(optionalImplementedMethods)) != nil;
}
/// The optional protocol selectors the user has added via -addImplementedOptionalProtocolMethods:
- (NSMutableSet<NSString *> *)optionalImplementedMethods
{
NSMutableSet *result = objc_getAssociatedObject(self, _cmd);
if (result == nil) {
result = [NSMutableSet set];
objc_setAssociatedObject(self, _cmd, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return result;
}
- (void)addImplementedOptionalProtocolMethods:(SEL)aSelector, ...
{
// Can't use isKindOfClass: since we're a proxy.
NSAssert(object_getClass(self) == OCMockObject.protocolMockObjectClass, @"Cannot call this method on non-protocol mocks.");
NSMutableSet *methods = self.optionalImplementedMethods;
// First arg is not returned by va_arg, needs to be handled separately.
if (aSelector != NULL) {
[methods addObject:NSStringFromSelector(aSelector)];
}
va_list args;
va_start(args, aSelector);
SEL s;
while((s = va_arg(args, SEL)))
{
[methods addObject:NSStringFromSelector(s)];
}
va_end(args);
}
- (BOOL)implementsOptionalProtocolMethod:(SEL)aSelector
{
NSAssert(self.hasSpecifiedOptionalProtocolMethods, @"Shouldn't call this method if the user hasn't opted-in to specifying optional protocol methods.");
// Check our collection first. It'll be in here if they explicitly marked the method as implemented.
for (NSString *str in self.optionalImplementedMethods) {
if (sel_isEqual(NSSelectorFromString(str), aSelector)) {
return YES;
}
}
// If they didn't explicitly mark it implemented, check if they stubbed/expected it. That counts too, but
// we still want them to have the option to declare that the method exists without
// stubbing it or making an expectation, so the rest of OCMock's mechanisms work as expected.
return [self handleSelector:aSelector];
}
- (BOOL)swizzled_protocolMockRespondsToSelector:(SEL)aSelector
{
// Can't use isKindOfClass: since we're a proxy.
NSAssert(object_getClass(self) == OCMockObject.protocolMockObjectClass, @"Swizzled method should only ever be called for protocol mocks.");
// If they haven't called our public method to opt-in, use the default behavior.
if (!self.hasSpecifiedOptionalProtocolMethods) {
return [self swizzled_protocolMockRespondsToSelector:aSelector];
}
Ivar i = class_getInstanceVariable([self class], "mockedProtocol");
NSAssert(i != NULL, nil);
Protocol *mockedProtocol = object_getIvar(self, i);
NSAssert(mockedProtocol != NULL, nil);
// Check if it's an optional protocol method. If not, just return the default implementation (which has now swapped).
struct objc_method_description methodDescription;
methodDescription = protocol_getMethodDescription(mockedProtocol, aSelector, NO, YES);
if (methodDescription.name == NULL) {
methodDescription = protocol_getMethodDescription(mockedProtocol, aSelector, NO, NO);
if (methodDescription.name == NULL) {
return [self swizzled_protocolMockRespondsToSelector:aSelector];
}
}
// It's an optional instance or class method. Override the return value.
return [self implementsOptionalProtocolMethod:aSelector];
}
// Whenever a mock object is initted, register it with the current test case
// so that it gets verified and its invocations are cleared during -tearDown.
- (instancetype)swizzled_init
{
[self swizzled_init];
[ASTestCase.currentTestCase registerMockObject:self];
return self;
}
- (NSString *)swizzled_classMockDescription
{
NSString *orig = [self swizzled_classMockDescription];
__auto_type block = self.modifyDescriptionBlock;
if (block) {
return block(self, orig);
}
return orig;
}
- (void)setModifyDescriptionBlock:(NSString *(^)(OCMockObject *, NSString *))modifyDescriptionBlock
{
objc_setAssociatedObject(self, @selector(modifyDescriptionBlock), modifyDescriptionBlock, OBJC_ASSOCIATION_COPY);
}
- (NSString *(^)(OCMockObject *, NSString *))modifyDescriptionBlock
{
return objc_getAssociatedObject(self, _cmd);
}
@end
@implementation OCMStubRecorder (ASProperties)
@dynamic _ignoringNonObjectArgs;
- (OCMStubRecorder *(^)(void))_ignoringNonObjectArgs
{
id (^theBlock)(void) = ^ ()
{
return [self ignoringNonObjectArgs];
};
return theBlock;
}
@dynamic _onMainThread;
- (OCMStubRecorder *(^)(void))_onMainThread
{
id (^theBlock)(void) = ^ ()
{
return [self andDo:^(NSInvocation *invocation) {
ASDisplayNodeAssertMainThread();
}];
};
return theBlock;
}
@dynamic _offMainThread;
- (OCMStubRecorder *(^)(void))_offMainThread
{
id (^theBlock)(void) = ^ ()
{
return [self andDo:^(NSInvocation *invocation) {
ASDisplayNodeAssertNotMainThread();
}];
};
return theBlock;
}
@dynamic _andDebugBreak;
- (OCMStubRecorder *(^)(void))_andDebugBreak
{
id (^theBlock)(void) = ^ ()
{
return [self andDo:^(NSInvocation *invocation) {
debug_break();
}];
};
return theBlock;
}
@end