diff --git a/Source/OCMock/OCMMacroState.h b/Source/OCMock/OCMMacroState.h index 0e2e95d5..4d8ae5f6 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 02075b8c..5bad6471 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 didRecordInvocation] == 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 didRecordInvocation] == 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 51064450..5e10993b 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,9 @@ 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 929f8c45..99860bd1 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 217acdb5..b53221df 100644 --- a/Source/OCMock/OCMVerifier.h +++ b/Source/OCMock/OCMVerifier.h @@ -16,13 +16,11 @@ #import -@class OCMLocation; @class OCMQuantifier; @interface OCMVerifier : OCMRecorder -@property(strong) OCMLocation *location; -@property(strong) OCMQuantifier *quantifier; +@property(retain) OCMQuantifier *quantifier; - (id)withQuantifier:(OCMQuantifier *)quantifier; diff --git a/Source/OCMock/OCMVerifier.m b/Source/OCMock/OCMVerifier.m index 0f59aa4f..df64dd6a 100644 --- a/Source/OCMock/OCMVerifier.m +++ b/Source/OCMock/OCMVerifier.m @@ -44,12 +44,11 @@ - (id)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/OCMockMacros.h b/Source/OCMock/OCMockMacros.h index 22535572..04271752 100644 --- a/Source/OCMock/OCMockMacros.h +++ b/Source/OCMock/OCMockMacros.h @@ -37,7 +37,7 @@ #define OCMStub(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginStubMacro]; \ + [OCMMacroState beginStubMacroAtLocation:OCMMakeLocation(nil, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ @@ -55,7 +55,7 @@ #define OCMExpect(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginExpectMacro]; \ + [OCMMacroState beginExpectMacroAtLocation:OCMMakeLocation(nil, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ @@ -73,7 +73,7 @@ #define OCMReject(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginRejectMacro]; \ + [OCMMacroState beginRejectMacroAtLocation:OCMMakeLocation(nil, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ diff --git a/Source/OCMockTests/OCMockObjectMacroTests.m b/Source/OCMockTests/OCMockObjectMacroTests.m index 3c77854c..e63898d5 100644 --- a/Source/OCMockTests/OCMockObjectMacroTests.m +++ b/Source/OCMockTests/OCMockObjectMacroTests.m @@ -454,6 +454,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 {