blob: 57ff492209ec75a4bda67ceb6f604b5afe2d15a6 [file] [log] [blame] [edit]
/*
* Copyright (c) 2013-2015 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import <objc/runtime.h>
#if TARGET_OS_IPHONE
#define NSRect CGRect
#define NSZeroRect CGRectZero
#define NSMakeRect CGRectMake
#define valueWithRect valueWithCGRect
#endif
#pragma mark Helper classes
@interface TestClassWithSimpleMethod : NSObject
+ (NSUInteger)initializeCallCount;
- (NSString *)foo;
@end
@implementation TestClassWithSimpleMethod
static NSUInteger initializeCallCount = 0;
+ (void)initialize
{
initializeCallCount += 1;
}
+ (NSUInteger)initializeCallCount
{
return initializeCallCount;
}
- (NSString *)foo
{
return @"Foo";
}
@end
@interface TestClassThatCallsSelf : NSObject
{
int methodInt;
}
- (NSString *)method1;
- (NSString *)method2;
- (NSRect)methodRect1;
- (NSRect)methodRect2;
- (int)methodInt;
- (void)methodVoid;
- (void)setMethodInt:(int)anInt;
@end
@implementation TestClassThatCallsSelf
- (NSString *)method1
{
id retVal = [self method2];
return retVal;
}
- (NSString *)method2
{
return @"Foo";
}
- (NSRect)methodRect1
{
NSRect retVal = [self methodRect2];
return retVal;
}
- (NSRect)methodRect2
{
return NSMakeRect(10, 10, 10, 10);
}
- (int)methodInt
{
return methodInt;
}
- (void)methodVoid
{
}
- (void)setMethodInt:(int)anInt
{
methodInt = anInt;
}
@end
@interface NSObject(OCMCategoryForTesting)
- (NSString *)categoryMethod;
@end
@implementation NSObject(OCMCategoryForTesting)
- (NSString *)categoryMethod
{
return @"Foo-Category";
}
@end
@interface OCMockObjectPartialMocksTests : XCTestCase
{
int numKVOCallbacks;
}
@end
@implementation OCMockObjectPartialMocksTests
#pragma mark Tests for stubbing with partial mocks
- (void)testStubsMethodsOnPartialMock
{
TestClassWithSimpleMethod *object = [[TestClassWithSimpleMethod alloc] init];
id mock = [OCMockObject partialMockForObject:object];
[[[mock stub] andReturn:@"hi"] foo];
XCTAssertEqualObjects(@"hi", [mock foo], @"Should have returned stubbed value");
}
- (void)testForwardsUnstubbedMethodsCallsToRealObjectOnPartialMock
{
TestClassWithSimpleMethod *object = [[TestClassWithSimpleMethod alloc] init];
id mock = [OCMockObject partialMockForObject:object];
XCTAssertEqualObjects(@"Foo", [mock foo], @"Should have returned value from real object.");
}
//- (void)testForwardsUnstubbedMethodsCallsToRealObjectOnPartialMockForTollFreeBridgedClasses
//{
// mock = [OCMockObject partialMockForObject:[NSString stringWithString:@"hello2"]];
// STAssertEqualObjects(@"HELLO2", [mock uppercaseString], @"Should have returned value from real object.");
//}
- (void)testStubsMethodOnRealObjectReference
{
TestClassWithSimpleMethod *realObject = [[TestClassWithSimpleMethod alloc] init];
id mock = [OCMockObject partialMockForObject:realObject];
[[[mock stub] andReturn:@"TestFoo"] foo];
XCTAssertEqualObjects(@"TestFoo", [realObject foo], @"Should have stubbed method.");
}
- (void)testCallsToSelfInRealObjectAreShadowedByPartialMock
{
TestClassThatCallsSelf *realObject = [[TestClassThatCallsSelf alloc] init];
id mock = [OCMockObject partialMockForObject:realObject];
[[[mock stub] andReturn:@"FooFoo"] method2];
XCTAssertEqualObjects(@"FooFoo", [mock method1], @"Should have called through to stubbed method.");
}
- (void)testCallsToSelfInRealObjectStructReturnAreShadowedByPartialMock
{
TestClassThatCallsSelf *realObject = [[TestClassThatCallsSelf alloc] init];
id mock = [OCMockObject partialMockForObject:realObject];
[[[mock stub] andReturnValue:OCMOCK_VALUE(NSZeroRect)] methodRect2];
#if TARGET_OS_IPHONE
#define NSEqualRects CGRectEqualToRect
#endif
XCTAssertTrue(NSEqualRects(NSZeroRect, [mock methodRect1]), @"Should have called through to stubbed method.");
}
- (void)testInvocationsOfNSObjectCategoryMethodsCanBeStubbed
{
TestClassThatCallsSelf *realObject = [[TestClassThatCallsSelf alloc] init];
id mock = [OCMockObject partialMockForObject:realObject];
[[[mock stub] andReturn:@"stubbed"] categoryMethod];
XCTAssertEqualObjects(@"stubbed", [realObject categoryMethod], @"Should have stubbed NSObject's method");
}
#pragma mark Tests for behaviour when setting up partial mocks
- (void)testPartialMockClassOverrideReportsOriginalClass
{
TestClassThatCallsSelf *realObject = [[TestClassThatCallsSelf alloc] init];
Class origClass = [realObject class];
id mock = [OCMockObject partialMockForObject:realObject];
XCTAssertEqualObjects([realObject class], origClass, @"Override of -class method did not work");
XCTAssertEqualObjects([mock class], origClass, @"Mock proxy -class method did not work");
XCTAssertFalse(origClass == object_getClass(realObject), @"Subclassing did not work");
[mock stopMocking];
XCTAssertEqualObjects([realObject class], origClass, @"Classes different after stopMocking");
XCTAssertEqualObjects(object_getClass(realObject), origClass, @"Classes different after stopMocking");
}
- (void)testInitializeIsNotCalledOnMockedClass
{
NSUInteger countBefore = [TestClassWithSimpleMethod initializeCallCount];
TestClassWithSimpleMethod *object = [[TestClassWithSimpleMethod alloc] init];
id mock = [OCMockObject partialMockForObject:object];
[[[mock expect] andForwardToRealObject] foo];
[object foo];
NSUInteger countAfter = [TestClassWithSimpleMethod initializeCallCount];
XCTAssertEqual(countBefore, countAfter, @"Creating a mock should not have resulted in call to +initialize");
}
- (void)testRefusesToCreateTwoPartialMocksForTheSameObject
{
id object = [[TestClassThatCallsSelf alloc] init];
id partialMock1 = [OCMockObject partialMockForObject:object];
XCTAssertNotNil(partialMock1, @"Should have created first partial mock.");
XCTAssertThrows([OCMockObject partialMockForObject:object], @"Should not have allowed creation of second partial mock");
}
- (void)testRefusesToCreatePartialMockForTollFreeBridgedClasses
{
id object = (id)CFBridgingRelease(CFStringCreateWithCString(kCFAllocatorDefault, "foo", kCFStringEncodingASCII));
XCTAssertThrowsSpecificNamed([OCMockObject partialMockForObject:object],
NSException,
NSInvalidArgumentException,
@"should throw NSInvalidArgumentException exception");
}
#if TARGET_RT_64_BIT
- (void)testRefusesToCreatePartialMockForTaggedPointers
{
NSDate *object = [NSDate dateWithTimeIntervalSince1970:0];
XCTAssertThrowsSpecificNamed([OCMockObject partialMockForObject:object],
NSException,
NSInvalidArgumentException,
@"should throw NSInvalidArgumentException exception");
}
#endif
- (void)testRefusesToCreatePartialMockForNilObject
{
XCTAssertThrows(OCMPartialMock(nil));
}
#pragma mark Tests for KVO interaction with mocks
/* Starting KVO observations on an already-mocked object generally should work. */
- (void)testAddingKVOObserverOnPartialMock
{
static char *MyContext;
TestClassThatCallsSelf *realObject = [[TestClassThatCallsSelf alloc] init];
Class origClass = [realObject class];
id mock = [OCMockObject partialMockForObject:realObject];
Class ourSubclass = object_getClass(realObject);
[realObject addObserver:self forKeyPath:@"methodInt" options:NSKeyValueObservingOptionNew context:MyContext];
Class kvoClass = object_getClass(realObject);
/* KVO additionally overrides the -class method, but they return the superclass of their special
subclass, which in this case is the special mock subclass */
XCTAssertEqualObjects([realObject class], ourSubclass, @"KVO override of class did not return our subclass");
XCTAssertFalse(ourSubclass == kvoClass, @"KVO with subclass did not work");
[realObject setMethodInt:45];
XCTAssertEqual(numKVOCallbacks, 1, @"did not get subclass KVO notification");
[mock setMethodInt:47];
XCTAssertEqual(numKVOCallbacks, 2, @"did not get mock KVO notification");
[realObject removeObserver:self forKeyPath:@"methodInt" context:MyContext];
XCTAssertEqualObjects([realObject class], origClass, @"Classes different after stopKVO");
XCTAssertEqualObjects(object_getClass(realObject), ourSubclass, @"Classes different after stopKVO");
[mock stopMocking];
XCTAssertEqualObjects([realObject class], origClass, @"Classes different after stopMocking");
XCTAssertEqualObjects(object_getClass(realObject), origClass, @"Classes different after stopMocking");
}
/* Mocking a class which already has KVO observations does not work, but does not crash. */
- (void)testPartialMockOnKVOObserved
{
static char *MyContext;
TestClassThatCallsSelf *realObject = [[TestClassThatCallsSelf alloc] init];
Class origClass = [realObject class];
[realObject addObserver:self forKeyPath:@"methodInt" options:NSKeyValueObservingOptionNew context:MyContext];
Class kvoClass = object_getClass(realObject);
id mock = [OCMockObject partialMockForObject:realObject];
Class ourSubclass = object_getClass(realObject);
XCTAssertEqualObjects([realObject class], origClass, @"We did not preserve the original [self class]");
XCTAssertFalse(ourSubclass == kvoClass, @"KVO with subclass did not work");
/* Due to the way we replace the object's class, the KVO class gets overwritten and
KVO notifications stop functioning. If we did not do this, the presence of the mock
subclass would cause KVO to crash, at least without further tinkering. */
[realObject setMethodInt:45];
// STAssertEquals(numKVOCallbacks, 1, @"did not get subclass KVO notification");
XCTAssertEqual(numKVOCallbacks, 0, @"got subclass KVO notification");
[mock setMethodInt:47];
// STAssertEquals(numKVOCallbacks, 2, @"did not get mock KVO notification");
XCTAssertEqual(numKVOCallbacks, 0, @"got mock KVO notification");
[mock stopMocking];
XCTAssertEqualObjects([realObject class], origClass, @"Classes different after stopMocking");
// STAssertEqualObjects(object_getClass(realObject), kvoClass, @"KVO class different after stopMocking");
XCTAssertEqualObjects(object_getClass(realObject), origClass, @"class different after stopMocking");
[realObject removeObserver:self forKeyPath:@"methodInt" context:MyContext];
XCTAssertEqualObjects([realObject class], origClass, @"Classes different after stopKVO");
XCTAssertEqualObjects(object_getClass(realObject), origClass, @"Classes different after stopKVO");
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
numKVOCallbacks++;
}
#pragma mark Tests for end of stubbing with partial mocks
- (void)testReturnsToRealImplementationWhenExpectedCallOccurred
{
TestClassWithSimpleMethod *realObject = [[TestClassWithSimpleMethod alloc] init];
id mock = [OCMockObject partialMockForObject:realObject];
[[[mock expect] andReturn:@"TestFoo"] foo];
XCTAssertEqualObjects(@"TestFoo", [realObject foo], @"Should have stubbed method.");
XCTAssertEqualObjects(@"Foo", [realObject foo], @"Should have 'unstubbed' method.");
}
- (void)testRestoresObjectWhenStopped
{
TestClassWithSimpleMethod *realObject = [[TestClassWithSimpleMethod alloc] init];
id mock = [OCMockObject partialMockForObject:realObject];
[[[mock stub] andReturn:@"TestFoo"] foo];
XCTAssertEqualObjects(@"TestFoo", [realObject foo], @"Should have stubbed method.");
XCTAssertEqualObjects(@"TestFoo", [realObject foo], @"Should have stubbed method.");
[mock stopMocking];
XCTAssertEqualObjects(@"Foo", [realObject foo], @"Should have 'unstubbed' method.");
}
#pragma mark Tests for explicit forward to real object with partial mocks
- (void)testForwardsToRealObjectWhenSetUpAndCalledOnMock
{
TestClassWithSimpleMethod *realObject = [[TestClassWithSimpleMethod alloc] init];
id mock = [OCMockObject partialMockForObject:realObject];
[[[mock expect] andForwardToRealObject] foo];
XCTAssertEqual(@"Foo", [mock foo], @"Should have called method on real object.");
[mock verify];
}
- (void)testForwardsToRealObjectWhenSetUpAndCalledOnRealObject
{
TestClassWithSimpleMethod *realObject = [[TestClassWithSimpleMethod alloc] init];
id mock = [OCMockObject partialMockForObject:realObject];
[[[mock expect] andForwardToRealObject] foo];
XCTAssertEqual(@"Foo", [realObject foo], @"Should have called method on real object.");
[mock verify];
}
- (void)testReturnValueFromRealObjectShouldBeReturnedEvenWithPrecedingAndCall
{
TestClassThatCallsSelf *object = [[TestClassThatCallsSelf alloc] init];
OCMockObject *mock = OCMPartialMock(object);
[[[[mock stub] andCall:@selector(firstReturnValueMethod) onObject:self] andForwardToRealObject] method2];
XCTAssertEqualObjects([object method2], @"Foo", @"Should have returned value from real object.");
}
- (NSString *)firstReturnValueMethod
{
return @"Bar";
}
- (void)testExpectedMethodCallsExpectedMethodWithExpectationOrdering
{
TestClassThatCallsSelf *object = [[TestClassThatCallsSelf alloc] init];
id mock = OCMPartialMock(object);
[mock setExpectationOrderMatters:YES];
[[[mock expect] andForwardToRealObject] method1];
[[[mock expect] andForwardToRealObject] method2];
XCTAssertNoThrow([object method1], @"Calling an expected method that internally calls another expected method should not make expectations appear to be out of order.");
}
#pragma mark Tests for method swizzling with partial mocks
- (NSString *)differentMethodInDifferentClass
{
return @"swizzled!";
}
- (void)testImplementsMethodSwizzling
{
// using partial mocks and the indirect return value provider
TestClassThatCallsSelf *foo = [[TestClassThatCallsSelf alloc] init];
id mock = [OCMockObject partialMockForObject:foo];
[[[mock stub] andCall:@selector(differentMethodInDifferentClass) onObject:self] method1];
XCTAssertEqualObjects(@"swizzled!", [foo method1], @"Should have returned value from different method");
}
- (void)aMethodWithVoidReturn
{
}
- (void)testMethodSwizzlingWorksForVoidReturns
{
TestClassThatCallsSelf *foo = [[TestClassThatCallsSelf alloc] init];
id mock = [OCMockObject partialMockForObject:foo];
[[[mock stub] andCall:@selector(aMethodWithVoidReturn) onObject:self] methodVoid];
XCTAssertNoThrow([foo method1], @"Should have worked.");
}
@end