|
31 | 31 | #import "OCMExpectationRecorder.h"
|
32 | 32 | #import "OCMQuantifier.h"
|
33 | 33 |
|
| 34 | +@class XCTestCase; |
34 | 35 |
|
35 |
| -@implementation OCMockObject |
| 36 | +static NSHashTable<OCMockObject *> *gTestCaseMocksToStop; |
| 37 | +static NSHashTable<OCMockObject *> *gTestSuiteMocksToStop; |
| 38 | +static NSHashTable<OCMockObject *> *gCurrentMocksToStopRecorder; |
| 39 | + |
| 40 | +static BOOL gAssertOnCallsAfterStopMocking; |
| 41 | + |
| 42 | +@protocol OCMockXCTestObservation |
| 43 | ++ (id)sharedTestObservationCenter; |
| 44 | +- (void)addTestObserver:(id)observer; |
| 45 | +@end |
| 46 | + |
| 47 | +@interface OCMockXCTestObserver : NSObject |
| 48 | +@end |
36 | 49 |
|
| 50 | +@implementation OCMockObject |
37 | 51 | #pragma mark Class initialisation
|
| 52 | ++ (void)load |
| 53 | +{ |
| 54 | + gTestCaseMocksToStop = [[NSHashTable weakObjectsHashTable] retain]; |
| 55 | + gTestSuiteMocksToStop = [[NSHashTable weakObjectsHashTable] retain]; |
| 56 | + gCurrentMocksToStopRecorder = gTestSuiteMocksToStop; |
| 57 | + gAssertOnCallsAfterStopMocking = YES; |
| 58 | + Class xctest = NSClassFromString(@"XCTestObservationCenter"); |
| 59 | + if (xctest) |
| 60 | + { |
| 61 | + // If XCTest is available, we set up an observer to stop our mocks for us. |
| 62 | + [[xctest sharedTestObservationCenter] addTestObserver:[[OCMockXCTestObserver alloc] init]]; |
| 63 | + } |
| 64 | + |
| 65 | +} |
38 | 66 |
|
39 | 67 | + (void)initialize
|
40 | 68 | {
|
41 |
| - if([[NSInvocation class] instanceMethodSignatureForSelector:@selector(getArgumentAtIndexAsObject:)] == NULL) |
42 |
| - [NSException raise:NSInternalInconsistencyException format:@"** Expected method not present; the method getArgumentAtIndexAsObject: is not implemented by NSInvocation. If you see this exception it is likely that you are using the static library version of OCMock and your project is not configured correctly to load categories from static libraries. Did you forget to add the -ObjC linker flag?"]; |
| 69 | + if (self == [OCMockObject class]) { |
| 70 | + if([[NSInvocation class] instanceMethodSignatureForSelector:@selector(getArgumentAtIndexAsObject:)] == NULL) |
| 71 | + { |
| 72 | + [NSException raise:NSInternalInconsistencyException format:@"** Expected method not present; the method getArgumentAtIndexAsObject: is not implemented by NSInvocation. If you see this exception it is likely that you are using the static library version of OCMock and your project is not configured correctly to load categories from static libraries. Did you forget to add the -ObjC linker flag?"]; |
| 73 | + } |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +#pragma mark Mock cleanup recording |
| 78 | + |
| 79 | ++ (void)recordAMockToStop:(OCMockObject *)mock { |
| 80 | + [gCurrentMocksToStopRecorder addObject:mock]; |
| 81 | +} |
| 82 | + |
| 83 | ++ (void)removeAMockToStop:(OCMockObject *)mock { |
| 84 | + [gCurrentMocksToStopRecorder removeObject:mock]; |
| 85 | +} |
| 86 | + |
| 87 | ++ (void)stopAllTestCaseMocks { |
| 88 | + for (OCMockObject *mock in gTestCaseMocksToStop) |
| 89 | + { |
| 90 | + [mock stopMocking]; |
| 91 | + } |
| 92 | + [gTestCaseMocksToStop removeAllObjects]; |
| 93 | +} |
| 94 | + |
| 95 | ++ (void)stopAllTestSuiteMocks { |
| 96 | + for (OCMockObject *mock in gTestSuiteMocksToStop) |
| 97 | + { |
| 98 | + [mock stopMocking]; |
| 99 | + } |
| 100 | + [gTestSuiteMocksToStop removeAllObjects]; |
43 | 101 | }
|
44 | 102 |
|
45 | 103 |
|
@@ -109,6 +167,7 @@ - (instancetype)init
|
109 | 167 | expectations = [[NSMutableArray alloc] init];
|
110 | 168 | exceptions = [[NSMutableArray alloc] init];
|
111 | 169 | invocations = [[NSMutableArray alloc] init];
|
| 170 | + [OCMockObject recordAMockToStop:self]; |
112 | 171 | return self;
|
113 | 172 | }
|
114 | 173 |
|
@@ -144,7 +203,7 @@ - (void)addExpectation:(OCMInvocationExpectation *)anExpectation
|
144 | 203 |
|
145 | 204 | - (void)assertInvocationsArrayIsPresent
|
146 | 205 | {
|
147 |
| - if(invocations == nil) { |
| 206 | + if(gAssertOnCallsAfterStopMocking && invocations == nil) { |
148 | 207 | [NSException raise:NSInternalInconsistencyException format:@"** Cannot handle or verify invocations on %@ at %p. This error usually occurs when a mock object is used after stopMocking has been called on it. In most cases it is not necessary to call stopMocking. If you know you have to, please make sure that the mock object is not used afterwards.", [self description], self];
|
149 | 208 | }
|
150 | 209 | }
|
@@ -508,4 +567,46 @@ - (NSString *)_stubDescriptions:(BOOL)onlyExpectations
|
508 | 567 | }
|
509 | 568 |
|
510 | 569 |
|
| 570 | +@end |
| 571 | + |
| 572 | +/** |
| 573 | + * The observer gets installed the first time a mock object is created (see +[OCMockObject initialize] |
| 574 | + * It stops all the mocks that are still active when the testcase has finished. |
| 575 | + * In many cases this should break a lot of retain loops and allow mocks to be freed. |
| 576 | + * More importantly this will remove mocks that have mocked a class method and persist across testcases. |
| 577 | + * It intentionally turns off the assert that fires when calling a mock after stopMocking has been |
| 578 | + * called on it, because when we are doing cleanup there are cases in dealloc methods where a mock |
| 579 | + * may be called. We allow the "assert off" state to persist beyond the end of -testCaseDidFinish |
| 580 | + * because objects may be destroyed by the autoreleasepool that wraps the entire test and this may |
| 581 | + * cause mocks to be called. The state is global (instead of per mock) because we want to be able |
| 582 | + * to catch the case where a mock is trapped by some global state (e.g. a non-mock singleton) and |
| 583 | + * then that singleton is used in a later test and attempts to call a stopped mock. |
| 584 | + **/ |
| 585 | +@implementation OCMockXCTestObserver |
| 586 | + |
| 587 | +- (BOOL)conformsToProtocol:(Protocol *)aProtocol { |
| 588 | + // This allows us to avoid linking XCTest into OCMock. |
| 589 | + return strcmp(protocol_getName(aProtocol), "XCTestObservation") == 0; |
| 590 | +} |
| 591 | + |
| 592 | +- (void)testSuiteWillStart:(XCTestCase *)testCase { |
| 593 | + gAssertOnCallsAfterStopMocking = YES; |
| 594 | + gCurrentMocksToStopRecorder = gTestSuiteMocksToStop; |
| 595 | +} |
| 596 | + |
| 597 | +- (void)testSuiteDidFinish:(XCTestCase *)testCase { |
| 598 | + gAssertOnCallsAfterStopMocking = NO; |
| 599 | + [OCMockObject stopAllTestSuiteMocks]; |
| 600 | +} |
| 601 | + |
| 602 | +- (void)testCaseWillStart:(XCTestCase *)testCase { |
| 603 | + gAssertOnCallsAfterStopMocking = YES; |
| 604 | + gCurrentMocksToStopRecorder = gTestCaseMocksToStop; |
| 605 | +} |
| 606 | + |
| 607 | +- (void)testCaseDidFinish:(XCTestCase *)testCase { |
| 608 | + gAssertOnCallsAfterStopMocking = NO; |
| 609 | + [OCMockObject stopAllTestCaseMocks]; |
| 610 | +} |
| 611 | + |
511 | 612 | @end
|
0 commit comments