mirror of
https://github.com/zhigang1992/Kiwi.git
synced 2026-04-28 20:15:40 +08:00
385 lines
18 KiB
Objective-C
385 lines
18 KiB
Objective-C
//
|
|
// Licensed under the terms in License.txt
|
|
//
|
|
// Copyright 2010 Allen Ding. All rights reserved.
|
|
//
|
|
|
|
#import "Kiwi.h"
|
|
#import "KiwiTestConfiguration.h"
|
|
#import "TestClasses.h"
|
|
#import "KWIntercept.h"
|
|
#import "NSMethodSignature+KiwiAdditions.h"
|
|
|
|
#if KW_TESTS_ENABLED
|
|
|
|
@interface KWMockTest : XCTestCase
|
|
|
|
@end
|
|
|
|
@implementation KWMockTest
|
|
|
|
- (void)tearDown {
|
|
KWClearStubsAndSpies();
|
|
}
|
|
|
|
- (void)testItShouldInitializeForAClassWithANameAsANullObject {
|
|
id mockedClass = [Cruiser class];
|
|
id name = @"Car mock";
|
|
id mock = [KWMock nullMockWithName:name forClass:mockedClass];
|
|
XCTAssertNotNil(mock, @"expected a mock object to be initialized");
|
|
XCTAssertEqualObjects([mock mockedClass], mockedClass, @"expected the mockedClass property to be set");
|
|
XCTAssertTrue([mock isNullMock], @"expected the isNullObject property to be set");
|
|
XCTAssertEqualObjects([mock mockName], @"Car mock", @"expected class mock to have the correct mockName");
|
|
}
|
|
|
|
- (void)testItShouldInitializeForAProtocolWithANameAsANullObject {
|
|
id mockedProtocol = @protocol(JumpCapable);
|
|
id name = @"JumpCapable mock";
|
|
id mock = [KWMock nullMockWithName:name forProtocol:mockedProtocol];
|
|
XCTAssertNotNil(mock, @"expected a mock object to be initialized");
|
|
XCTAssertEqualObjects([mock mockedProtocol], mockedProtocol, @"expected the mockedProtocol property to be set");
|
|
XCTAssertTrue([mock isNullMock], @"expected the isNullObject property to be set");
|
|
XCTAssertEqualObjects([mock mockName], @"JumpCapable mock", @"expected class mock to have the correct mockName");
|
|
}
|
|
|
|
- (void)testItShouldInitializeAPartialMockForAClass {
|
|
id mockedObject = [[Cruiser alloc] init];
|
|
id name = @"Cruiser mock";
|
|
id mock = [KWMock partialMockWithName:name forObject:mockedObject];
|
|
XCTAssertNotNil(mock, @"expected a mock object to be initialized");
|
|
XCTAssertEqualObjects([mock mockedObject], mockedObject, @"expected the mockedClass property to be set");
|
|
XCTAssertTrue([mock isPartialMock], @"expected the isPartialMock property to be set");
|
|
}
|
|
|
|
- (void)testItShouldPassThroughAMessageToTheMockedObjectAsAPartialMock {
|
|
id callsign = @"Object Callsign";
|
|
id mockedObject = [[Cruiser alloc] initWithCallsign:callsign];
|
|
id mock = [KWMock partialMockForObject:mockedObject];
|
|
id returnedCallsign = [mock callsign];
|
|
XCTAssertEqualObjects(returnedCallsign, callsign, @"expected the partial mock to pass through a message to the object");
|
|
}
|
|
|
|
//- (void)testItShouldRaiseWhenReceivingUnexpectedMessageAsAMock {
|
|
// id mock = [KWMock mockForClass:[Cruiser class]];
|
|
// STAssertThrows([mock objectAtIndex:0], @"expected mock to raise exception");
|
|
//}
|
|
|
|
- (void)testItShouldNotRaiseWhenReceivingUnexpectedMessageAsANullMock {
|
|
id mock = [Cruiser nullMock];
|
|
XCTAssertNoThrow([mock raiseShields], @"expected null mock not to raise when receiving unexpected message");
|
|
}
|
|
|
|
- (void)testItShouldStubWithASelector {
|
|
id mock = [Cruiser mock];
|
|
[mock stub:@selector(raiseShields)];
|
|
XCTAssertEqual([mock raiseShields], NO, @"expected method to be stubbed with the correct value");
|
|
}
|
|
|
|
- (void)testItShouldStubTheNameMethodOnAClassMock {
|
|
id mock = [Galaxy mock];
|
|
[[mock stubAndReturn:@"fake galaxy mockName"] name];
|
|
XCTAssertEqualObjects([mock name], @"fake galaxy mockName", @"expected mockName property to return the stub value");
|
|
}
|
|
|
|
- (void)testItShouldBeOkToStubOnSingletons {
|
|
TestSpy *firstSpy = [TestSpy testSpy];
|
|
KWMessagePattern *firstMessagePattern = [KWMessagePattern messagePatternWithSelector:@selector(notifyEarth)];
|
|
[[Galaxy sharedGalaxy] addMessageSpy:firstSpy forMessagePattern:firstMessagePattern];
|
|
|
|
KWClearStubsAndSpies();
|
|
|
|
TestSpy *secondSpy = [TestSpy testSpy];
|
|
KWMessagePattern *secondMessagePattern = [KWMessagePattern messagePatternWithSelector:@selector(notifyPlanet:)];
|
|
[[Galaxy sharedGalaxy] addMessageSpy:secondSpy forMessagePattern:secondMessagePattern];
|
|
|
|
[[Galaxy sharedGalaxy] notifyEarth];
|
|
|
|
XCTAssertTrue(secondSpy.wasNotified, @"expected first spy to never be called");
|
|
}
|
|
|
|
- (void)testItShouldStubWithASelectorAndReturnValue {
|
|
id mock = [Cruiser mock];
|
|
[mock stub:@selector(crewComplement) andReturn:[KWValue valueWithUnsignedInt:42]];
|
|
XCTAssertEqual([mock crewComplement], (NSUInteger)42, @"expected method to be stubbed with the correct value");
|
|
}
|
|
|
|
- (void)testItShouldStubWithASelectorReturnValueAndArguments {
|
|
id mock = [Cruiser nullMock];
|
|
[mock stub:@selector(energyLevelInWarpCore:) andReturn:theValue(30.0f) withArguments:theValue(3)];
|
|
XCTAssertEqual([mock energyLevelInWarpCore:3], 30.0f, @"expected method with arguments to be stubbed with the correct value");
|
|
XCTAssertTrue([mock energyLevelInWarpCore:2] != 30.0f, @"expected method with arguments not to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubWithASelectorReturnValueAndAnyArguments {
|
|
id mock = [Cruiser nullMock];
|
|
[mock stub:@selector(energyLevelInWarpCore:) andReturn:theValue(30.0f) withArguments:any()];
|
|
XCTAssertEqual([mock energyLevelInWarpCore:3], 30.0f, @"expected method with any() arguments to be stubbed");
|
|
XCTAssertEqual([mock energyLevelInWarpCore:2], 30.0f, @"expected method with any() arguments to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubWithAMessage {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertNoThrow([[mock stub] energyLevelInWarpCore:3], @"expected mock to stub message");
|
|
float ratio = [mock energyLevelInWarpCore:3];
|
|
XCTAssertEqual(ratio, 0.0f, @"expected mock to have message stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubWithAReturnValueAndMessage {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertNoThrow([[mock stubAndReturn:[KWValue valueWithFloat:42.0f]] energyLevelInWarpCore:3], @"expected mock to stub message");
|
|
float ratio = [mock energyLevelInWarpCore:3];
|
|
XCTAssertEqual(ratio, 42.0f, @"expected mock to have message stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubWithDifferentReturnValuesAndMessage {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertNoThrow([[mock stubAndReturn:[KWValue valueWithFloat:42.0f] times:[KWValue valueWithInt:2] afterThatReturn:[KWValue valueWithFloat:43.0f]] energyLevelInWarpCore:3], @"expected mock to stub message");
|
|
float firstRatio = [mock energyLevelInWarpCore:3];
|
|
XCTAssertEqual(firstRatio, 42.0f, @"expected mock to have message stubbed");
|
|
float secondRatio = [mock energyLevelInWarpCore:3];
|
|
XCTAssertEqual(secondRatio, 42.0f, @"expected mock to have message stubbed");
|
|
float thirdRatio = [mock energyLevelInWarpCore:3];
|
|
XCTAssertEqual(thirdRatio, 43.0f, @"expected mock to have message stubbed and changed return value");
|
|
}
|
|
|
|
- (void)testItShouldClearStubbedMethods {
|
|
id mock = [Cruiser mock];
|
|
[mock stub:@selector(callsign) andReturn:@"Executor"];
|
|
[mock clearStubs];
|
|
XCTAssertNil([mock callsign], @"expected method stub to be cleared");
|
|
}
|
|
|
|
- (void)testItShouldStubWhitelistedMethods {
|
|
id mock = [Cruiser mock];
|
|
[mock stub:@selector(isEqual:) andReturn:[KWValue valueWithBool:YES]];
|
|
[mock stub:@selector(hash) andReturn:[KWValue valueWithUnsignedInt:4242]];
|
|
[mock stub:@selector(description) andReturn:@"king rat"];
|
|
[mock stub:@selector(copy) andReturn:@"bacon"];
|
|
XCTAssertTrue([mock isEqual:@"foobar"], @"expected method to be stubbed");
|
|
XCTAssertEqual([mock hash], (NSUInteger)4242, @"expected method to be stubbed");
|
|
XCTAssertEqualObjects([mock description], @"king rat", @"expected method to be stubbed");
|
|
XCTAssertEqualObjects([mock copy], @"bacon", @"expected method to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubWithAsAPartialMock {
|
|
id mockedObject = [[Cruiser alloc] initWithCallsign:@"asdf"];
|
|
id mock = [KWMock partialMockForObject:mockedObject];
|
|
[mock stub:@selector(callsign) andReturn:@"test callsign"];
|
|
XCTAssertEqualObjects([mock callsign], @"test callsign", @"expected the partial mock to hit a stub when defined");
|
|
}
|
|
|
|
- (void)testItShouldNotRaiseForWhitelistedMethods {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertNoThrow([mock isEqual:mock], @"expected no exception");
|
|
XCTAssertNoThrow([mock hash], @"expected no exception");
|
|
XCTAssertNoThrow([mock description], @"expected no exception");
|
|
}
|
|
|
|
- (void)testItShouldNotifyMultipleSpiesWithDifferentMessagePatterns {
|
|
Cruiser *mock = [Cruiser mock];
|
|
TestSpy *spy1 = [TestSpy testSpy];
|
|
TestSpy *spy2 = [TestSpy testSpy];
|
|
KWMessagePattern *messagePattern1 = [KWMessagePattern messagePatternWithSelector:@selector(energyLevelInWarpCore:)];
|
|
NSArray *argumentFilters = @[[KWValue valueWithUnsignedInt:2]];
|
|
KWMessagePattern *messagePattern2 = [KWMessagePattern messagePatternWithSelector:@selector(energyLevelInWarpCore:) argumentFilters:argumentFilters];
|
|
|
|
[mock addMessageSpy:spy1 forMessagePattern:messagePattern1];
|
|
[mock addMessageSpy:spy2 forMessagePattern:messagePattern2];
|
|
[mock energyLevelInWarpCore:2];
|
|
|
|
XCTAssertTrue(spy1.wasNotified, @"expected object to notify spies");
|
|
XCTAssertTrue(spy2.wasNotified, @"expected object to notify spies");
|
|
}
|
|
|
|
- (void)testItShouldBeAKindOfMockedClass {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertTrue([mock isKindOfClass:[Cruiser class]], @"expected mock to be a kind of mocked class");
|
|
}
|
|
|
|
- (void)testItShouldBeAKindOfMockedClassAncestor {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertTrue([mock isKindOfClass:[SpaceShip class]], @"expected mock to be a kind of mocked class");
|
|
}
|
|
|
|
- (void)testItShouldBeAMemberOfMockedClass {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertTrue([mock isMemberOfClass:[Cruiser class]], @"expected mock to be a member of mocked class");
|
|
}
|
|
|
|
- (void)testItShouldRespondToSelectorsInMockedClasses {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertTrue([mock respondsToSelector:@selector(raiseShields)], @"expected mock to respond to selector");
|
|
XCTAssertTrue([mock respondsToSelector:@selector(energyLevelInWarpCore:)], @"expected mock to respond to selector");
|
|
XCTAssertFalse([mock respondsToSelector:@selector(objectAtIndex:)], @"expected mock not to respond to selector");
|
|
}
|
|
|
|
- (void)testItShouldConformToProtocolsThatMockedClassConformsTo {
|
|
id mock = [Cruiser mock];
|
|
XCTAssertTrue([mock conformsToProtocol:@protocol(JumpCapable)], @"expected mock to conform to protocol");
|
|
}
|
|
|
|
- (void)testItShouldReturnMethodSignaturesForMethodsOfMockedClasses {
|
|
id mock = [Cruiser mock];
|
|
NSMethodSignature *signature = [mock methodSignatureForSelector:@selector(computeStarHashForKey:)];
|
|
XCTAssertTrue(KWObjCTypeEqualToObjCType([signature methodReturnType], @encode(NSUInteger)), @"expected return types to match");
|
|
XCTAssertEqual([signature numberOfMessageArguments], (NSUInteger)1, @"expected number of arguments to match");
|
|
XCTAssertTrue(KWObjCTypeEqualToObjCType([signature messageArgumentTypeAtIndex:0], @encode(NSUInteger)), @"expected argument types to match");
|
|
}
|
|
|
|
- (void)testItShouldReturnResultsForMethodsOfMockedClass {
|
|
id mock = [Cruiser mock];
|
|
[mock stub:@selector(energyLevelInWarpCore:)];
|
|
XCTAssertEqual([mock energyLevelInWarpCore:4], 0.0f, @"expected method to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubAndReturnResultsForMethodsOfMockedClass {
|
|
id mock = [Cruiser mock];
|
|
[mock stub:@selector(energyLevelInWarpCore:) andReturn:[KWValue valueWithFloat:123.456f]];
|
|
XCTAssertEqual([mock energyLevelInWarpCore:41111], 123.456f, @"expected method to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldConformToMockedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
XCTAssertTrue([mock conformsToProtocol:@protocol(JumpCapable)], @"expected mock to conform to protocol");
|
|
}
|
|
|
|
- (void)testItShouldRespondToSelectorsInMockedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
XCTAssertTrue([mock respondsToSelector:@selector(computeParsecs)], @"expected mock to respond to selector");
|
|
XCTAssertTrue([mock respondsToSelector:@selector(engageHyperdrive)], @"expected mock to respond to selector");
|
|
XCTAssertTrue([mock respondsToSelector:@selector(hyperdriveFuelLevel)], @"expected mock to respond to selector");
|
|
XCTAssertFalse([mock respondsToSelector:@selector(exampleWillEnd)], @"expected mock not to respond to selector");
|
|
}
|
|
|
|
- (void)testItShouldConformToIndirectConformedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
XCTAssertTrue([mock conformsToProtocol:@protocol(OrbitCapable)], @"expected mock to conform to protocol");
|
|
}
|
|
|
|
- (void)testItShouldReturnMethodSignaturesForMethodsOfMockedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
NSMethodSignature *signature = [mock methodSignatureForSelector:@selector(hyperdriveFuelLevel)];
|
|
XCTAssertTrue(KWObjCTypeEqualToObjCType([signature methodReturnType], @encode(NSUInteger)), @"expected return types to match");
|
|
XCTAssertEqual([signature numberOfMessageArguments], (NSUInteger)0, @"expected number of arguments to match");
|
|
}
|
|
|
|
- (void)testItShouldReturnResultsForMethodsOfMockedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
[mock stub:@selector(hyperdriveFuelLevel)];
|
|
XCTAssertEqual([mock hyperdriveFuelLevel], (NSUInteger)0, @"expected method to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldReturnMethodSignaturesForMethodsOfIndirectConformedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
NSMethodSignature *signature = [mock methodSignatureForSelector:@selector(orbitPeriodForMass:)];
|
|
XCTAssertTrue(KWObjCTypeEqualToObjCType([signature methodReturnType], @encode(float)), @"expected return types to match");
|
|
XCTAssertEqual([signature numberOfMessageArguments], (NSUInteger)1, @"expected number of arguments to match");
|
|
XCTAssertTrue(KWObjCTypeEqualToObjCType([signature messageArgumentTypeAtIndex:0], @encode(float)), @"expected argument types to match");
|
|
}
|
|
|
|
- (void)testItShouldStubAndReturnResultsForMethodsOfMockedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
[mock stub:@selector(hyperdriveFuelLevel) andReturn:[KWValue valueWithUnsignedInt:4242]];
|
|
XCTAssertEqual([mock hyperdriveFuelLevel], (NSUInteger)4242, @"expected method to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldReturnResultsForMethodsOfIndirectConformedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
[mock stub:@selector(orbitPeriodForMass:)];
|
|
XCTAssertEqual([mock orbitPeriodForMass:20.0f], 0.0f, @"expected method to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubAndReturnResultsForMethodsOfIndirectConformedProtocols {
|
|
id mock = [KWMock mockForProtocol:@protocol(JumpCapable)];
|
|
[mock stub:@selector(orbitPeriodForMass:) andReturn:[KWValue valueWithFloat:100.0f]];
|
|
XCTAssertEqual([mock orbitPeriodForMass:20.0f], 100.0f, @"expected method to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubAndUnstubStubCopy {
|
|
id mock = [Cruiser mock];
|
|
[mock stub:@selector(copy) andReturn:mock];
|
|
id cruiser = [mock copy];
|
|
XCTAssertEqual(mock, cruiser, @"expected copy to be stubbed");
|
|
[mock clearStubs];
|
|
//STAssertThrows([mock copy], @"expected copy to be unstubbed"); // NSInvocation bug
|
|
}
|
|
|
|
- (void)testItShouldStubAndUnstubStubMutableCopy {
|
|
id mock = [Cruiser mock];
|
|
[mock stub:@selector(mutableCopy) andReturn:mock];
|
|
id cruiser = [mock mutableCopy];
|
|
XCTAssertEqual(mock, cruiser, @"expected copy to be stubbed");
|
|
[mock clearStubs];
|
|
//STAssertThrows([mock mutableCopy], @"expected copy to be unstubbed"); // NSInvocation bug
|
|
}
|
|
|
|
- (void)testItShouldStubInit {
|
|
id mock = [Cruiser nullMock];
|
|
id otherMock = [Cruiser mock];
|
|
[Cruiser stub:@selector(alloc) andReturn:mock];
|
|
[mock stub:@selector(init) andReturn:otherMock];
|
|
id cruiser = [[Cruiser alloc] init];
|
|
XCTAssertEqual(cruiser, otherMock, @"expected init to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldStubInitAndReturnSelfByDefaultForNullMocks {
|
|
id mock = [Cruiser nullMock];
|
|
[Cruiser stub:@selector(alloc) andReturn:mock];
|
|
id cruiser = [[Cruiser alloc] init];
|
|
XCTAssertEqual(cruiser, mock, @"expected init to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldNotRaiseWhenReceivingKVCMessagesAsANullMock {
|
|
id mock = [Cruiser nullMock];
|
|
XCTAssertNoThrow([mock valueForKey:@"foo"], @"expected valueForKey: not to raise");
|
|
XCTAssertNoThrow([mock setValue:@"bar" forKey:@"foo"], @"expected setValue:forKey not to raise");
|
|
XCTAssertNoThrow([mock valueForKeyPath:@"foo.bar"], @"expected valueForKeyPath: not to raise");
|
|
XCTAssertNoThrow([mock setValue:@"baz" forKeyPath:@"foo.bar"], @"expected setValue:forKeyPath: not to raise");
|
|
}
|
|
|
|
- (void)testItShouldAllowStubbingValueForKey {
|
|
id mock = [Cruiser mock];
|
|
id otherMock = [Cruiser mock];
|
|
[mock stub:@selector(valueForKey:) andReturn:otherMock withArguments:@"foo"];
|
|
id value = [mock valueForKey:@"foo"];
|
|
XCTAssertEqual(value, otherMock, @"expected valueForKey: to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldAllowStubbingValueForKeyPath {
|
|
id mock = [Cruiser mock];
|
|
id otherMock = [Cruiser mock];
|
|
[mock stub:@selector(valueForKeyPath:) andReturn:otherMock withArguments:@"foo.bar"];
|
|
id value = [mock valueForKeyPath:@"foo.bar"];
|
|
XCTAssertEqual(value, otherMock, @"expected valueForKeyPath: to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldAllowStubbingSetValueForKey {
|
|
id mock = [Cruiser mock];
|
|
__block BOOL called = NO;
|
|
[mock stub:@selector(setValue:forKey:) withBlock:^id(NSArray *params) {
|
|
XCTAssertEqual(params[0], @"baz", @"expected arg 1 of setValue:forKey: to be 'baz'");
|
|
XCTAssertEqual(params[1], @"foo", @"expected arg 2 of setValue:forKey: to be 'foo'");
|
|
called = YES;
|
|
return nil;
|
|
}];
|
|
[mock setValue:@"baz" forKey:@"foo"];
|
|
XCTAssertTrue(called, @"expected setValue:forKey: to be stubbed");
|
|
}
|
|
|
|
- (void)testItShouldAllowStubbingSetValueForKeyPath {
|
|
id mock = [Cruiser mock];
|
|
__block BOOL called = NO;
|
|
[mock stub:@selector(setValue:forKeyPath:) withBlock:^id(NSArray *params) {
|
|
XCTAssertEqual(params[0], @"baz", @"expected arg 1 of setValue:forKeyPath: to be 'baz'");
|
|
XCTAssertEqual(params[1], @"foo.bar", @"expected arg 2 of setValue:forKey: to be 'foo.bar'");
|
|
called = YES;
|
|
return nil;
|
|
}];
|
|
[mock setValue:@"baz" forKeyPath:@"foo.bar"];
|
|
XCTAssertTrue(called, @"expected setValue:forKeyPath: to be stubbed");
|
|
}
|
|
|
|
@end
|
|
|
|
#endif // #if KW_TESTS_ENABLED
|