diff --git a/Source/OCMock/OCMMacroState.h b/Source/OCMock/OCMMacroState.h index a5dda11a..48d623be 100644 --- a/Source/OCMock/OCMMacroState.h +++ b/Source/OCMock/OCMMacroState.h @@ -29,13 +29,13 @@ BOOL invocationDidThrow; } -+ (void)beginStubMacro; ++ (void)beginStubMacroAtLocation:(OCMLocation *)aLocation; + (OCMStubRecorder *)endStubMacro; -+ (void)beginExpectMacro; ++ (void)beginExpectMacroAtLocation:(OCMLocation *)aLocation; + (OCMStubRecorder *)endExpectMacro; -+ (void)beginRejectMacro; ++ (void)beginRejectMacroAtLocation:(OCMLocation *)aLocation; + (OCMStubRecorder *)endRejectMacro; + (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation; diff --git a/Source/OCMock/OCMMacroState.m b/Source/OCMock/OCMMacroState.m index 2842ab5d..aebfd2c6 100644 --- a/Source/OCMock/OCMMacroState.m +++ b/Source/OCMock/OCMMacroState.m @@ -25,9 +25,10 @@ @implementation OCMMacroState #pragma mark Methods to begin/end macros -+ (void)beginStubMacro ++ (void)beginStubMacroAtLocation:(OCMLocation *)aLocation { OCMStubRecorder *recorder = [[[OCMStubRecorder alloc] init] autorelease]; + recorder.ocm_location = aLocation; OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; [macroState release]; @@ -40,21 +41,31 @@ + (OCMStubRecorder *)endStubMacro OCMStubRecorder *recorder = [[(OCMStubRecorder *)[globalState recorder] retain] autorelease]; BOOL didThrow = [globalState invocationDidThrow]; [threadDictionary removeObjectForKey:OCMGlobalStateKey]; - if(didThrow == NO && [recorder wasUsed] == NO) - { - [NSException raise:NSInternalInconsistencyException - format:@"Did not record an invocation in OCMStub/OCMExpect/OCMReject.\n" - @"Possible causes are:\n" - @"- The receiver is not a mock object.\n" - @"- The selector conflicts with a selector implemented by OCMStubRecorder/OCMExpectationRecorder."]; - } + if(didThrow == NO && [recorder wasUsed] == NO) + { + OCMLocation *location = recorder.ocm_location; + NSString *explanation = @"Did not record an invocation in OCMStub/OCMExpect/OCMReject.\n" + @"Possible causes are:\n" + @"- The receiver is not a mock object.\n" + @"- The selector conflicts with a selector implemented by OCMStubRecorder/OCMExpectationRecorder."; + if(location != nil) + { + [NSException raise:NSInternalInconsistencyException + format:@"%@:%d :%@", [location file], (int)[location line], explanation]; + } + else + { + [NSException raise:NSInternalInconsistencyException format:@"%@", explanation]; + } + } return recorder; } -+ (void)beginExpectMacro ++ (void)beginExpectMacroAtLocation:(OCMLocation *)aLocation { OCMExpectationRecorder *recorder = [[[OCMExpectationRecorder alloc] init] autorelease]; + recorder.ocm_location = aLocation; OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; [macroState release]; @@ -66,9 +77,10 @@ + (OCMStubRecorder *)endExpectMacro } -+ (void)beginRejectMacro ++ (void)beginRejectMacroAtLocation:(OCMLocation *)aLocation { OCMExpectationRecorder *recorder = [[[OCMExpectationRecorder alloc] init] autorelease]; + recorder.ocm_location = aLocation; OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; [macroState release]; @@ -92,7 +104,7 @@ + (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation + (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation withQuantifier:(OCMQuantifier *)quantifier { OCMVerifier *recorder = [[[OCMVerifier alloc] init] autorelease]; - [recorder setLocation:aLocation]; + recorder.ocm_location = aLocation; [recorder setQuantifier:quantifier]; OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; diff --git a/Source/OCMock/OCMRecorder.h b/Source/OCMock/OCMRecorder.h index bcdf0fbd..a646c970 100644 --- a/Source/OCMock/OCMRecorder.h +++ b/Source/OCMock/OCMRecorder.h @@ -16,6 +16,8 @@ #import +#import "OCMLocation.h" + @class OCMockObject; @class OCMInvocationMatcher; @@ -28,6 +30,8 @@ BOOL shouldReturnMockFromInit; } +// Using `ocm_` prefix to minimize clashes with mocked objects using `location` as a property. +@property(retain) OCMLocation *ocm_location; - (instancetype)init; - (instancetype)initWithMockObject:(OCMockObject *)aMockObject; diff --git a/Source/OCMock/OCMRecorder.m b/Source/OCMock/OCMRecorder.m index dd175907..341d6b89 100644 --- a/Source/OCMock/OCMRecorder.m +++ b/Source/OCMock/OCMRecorder.m @@ -51,6 +51,7 @@ - (void)setShouldReturnMockFromInit:(BOOL)flag - (void)dealloc { + [_ocm_location release]; [invocationMatcher release]; [super dealloc]; } diff --git a/Source/OCMock/OCMVerifier.h b/Source/OCMock/OCMVerifier.h index 164904fe..49517fdf 100644 --- a/Source/OCMock/OCMVerifier.h +++ b/Source/OCMock/OCMVerifier.h @@ -15,13 +15,11 @@ */ #import "OCMRecorder.h" -#import "OCMLocation.h" #import "OCMQuantifier.h" @interface OCMVerifier : OCMRecorder -@property(retain) OCMLocation *location; @property(retain) OCMQuantifier *quantifier; - (instancetype)withQuantifier:(OCMQuantifier *)quantifier; diff --git a/Source/OCMock/OCMVerifier.m b/Source/OCMock/OCMVerifier.m index c9a06f9d..7f44827d 100644 --- a/Source/OCMock/OCMVerifier.m +++ b/Source/OCMock/OCMVerifier.m @@ -42,12 +42,11 @@ - (instancetype)withQuantifier:(OCMQuantifier *)quantifier - (void)forwardInvocation:(NSInvocation *)anInvocation { [super forwardInvocation:anInvocation]; - [mockObject verifyInvocation:invocationMatcher withQuantifier:self.quantifier atLocation:self.location]; + [mockObject verifyInvocation:invocationMatcher withQuantifier:self.quantifier atLocation:self.ocm_location]; } - (void)dealloc { - [_location release]; [_quantifier release]; [super dealloc]; } diff --git a/Source/OCMock/OCMock.h b/Source/OCMock/OCMock.h index 548e2147..70c3a2c3 100644 --- a/Source/OCMock/OCMock.h +++ b/Source/OCMock/OCMock.h @@ -48,7 +48,7 @@ #define OCMStub(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginStubMacro]; \ + [OCMMacroState beginStubMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ @@ -65,7 +65,7 @@ #define OCMExpect(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginExpectMacro]; \ + [OCMMacroState beginExpectMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ @@ -82,7 +82,7 @@ #define OCMReject(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginRejectMacro]; \ + [OCMMacroState beginRejectMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ diff --git a/Source/OCMockTests/OCMockObjectMacroTests.m b/Source/OCMockTests/OCMockObjectMacroTests.m index c4ba9f87..d1699996 100644 --- a/Source/OCMockTests/OCMockObjectMacroTests.m +++ b/Source/OCMockTests/OCMockObjectMacroTests.m @@ -390,6 +390,54 @@ - (void)testShouldHintAtPossibleReasonWhenVerifyingMethodThatCannotBeMocked } } +- (void)testStubExpectAndRejectShouldCaptureFileAndLineNumbers +{ + NSString *expectedFile = [NSString stringWithUTF8String:__FILE__]; + int expectedLine; + BOOL caughtException = NO; + id realObject = [NSMutableArray array]; + + @try + { + caughtException = NO; + expectedLine = __LINE__; OCMStub([realObject addObject:@"foo"]); + } + @catch (NSException *e) + { + XCTAssertTrue([[e reason] containsString:expectedFile], @"`%@` should contain `%@`", [e reason], expectedFile); + XCTAssertTrue([[e reason] containsString:[[NSNumber numberWithInt:expectedLine] stringValue]], @"`%@` should contain `%d`", [e reason], expectedLine); + caughtException = YES; + } + XCTAssertTrue(caughtException); + + @try + { + caughtException = NO; + expectedLine = __LINE__; OCMExpect([realObject addObject:@"foo"]); + } + @catch (NSException *e) + { + XCTAssertTrue([[e reason] containsString:expectedFile], @"`%@` should contain `%@`", [e reason], expectedFile); + XCTAssertTrue([[e reason] containsString:[[NSNumber numberWithInt:expectedLine] stringValue]], @"`%@` should contain `%d`", [e reason], expectedLine); + caughtException = YES; + } + XCTAssertTrue(caughtException); + + @try + { + caughtException = NO; + expectedLine = __LINE__; OCMReject([realObject addObject:@"foo"]); + } + @catch (NSException *e) + { + XCTAssertTrue([[e reason] containsString:expectedFile], @"`%@` should contain `%@`", [e reason], expectedFile); + XCTAssertTrue([[e reason] containsString:[[NSNumber numberWithInt:expectedLine] stringValue]], @"`%@` should contain `%d`", [e reason], expectedLine); + caughtException = YES; + } + XCTAssertTrue(caughtException); + +} + - (void)testCanExplicitlySelectClassMethodForStubs {