2011-02-03 15 views
5
- (BOOL)coolMethod:(NSString*)str 
{ 
    //do some stuff 
    Webservice *ws = [[WebService alloc] init]; 
    NSString *result = [ws startSynchronous:url]; 
    if ([result isEqual:@"Something"]) 
    { 
     //More calculation 
     return YES; 
    } 
    return NO; 
} 

私はOCUnitを使用しています 次の方法では、WebServiceオブジェクトまたはその結果をメソッドstartSynchronousに偽装して独立した単体テストを作成する方法はありますか?目的C - ユニットテスト&モッキングオブジェクト?

モックWebサービスを作成するか、startSynchronousコールでモックデータを返すために、そこにコードを挿入することはできますか?

答えて

4

一つの方法は、あなたも、モックオブジェクトを返すためにinitメソッドをオーバーライドすることができ、カテゴリを使用して、必要なメソッドをオーバーライドすることです:

@interface Webservice (Mock) 
- (id)init; 
@end 

@implementation Webservice (Mock) 
- (id)init 
{ 
    //WebServiceMock is a subclass of WebService 
    WebServiceMock *moc = [[WebServiceMock alloc] init]; 
    return (Webservice*)moc; 
} 
@end 

これで問題は、あなたがオブジェクトのリターンを作りたい場合1つのテストファイルで異なるテストで異なる結果を出すことはできません。

EDIT(あなたは一度テストページあたりの各メソッドをオーバーライドすることができます):これは私が掲示古い質問です

を、私は私がテスト可能なコードを書くと、ユニットは、今日それをテストする方法に答えを更新するだろうと思いました: )

のViewControllerコード

@implementation MyViewController 
@synthesize webService; 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    [self.webService sendSomeMessage:@"Some_Message"]; 
} 

- (WebService *)webService 
{ 
    if (!_webService) 
     _webService = [[WebService alloc] init]; 

    return _webService; 
} 

@end 

テストコード

@implementation MyViewControllerTest 

- (void)testCorrectMessageIsSentToServer 
{ 
    MyViewController *vc = [[MyViewController alloc] init]; 
    vc.webService = [OCMock niceMockForClass:[WebService class]]; 

    [[(OCMockObject *)vc.webService expect] [email protected]"Some_Message"]; 
    [vc view]; /* triggers viewDidLoad */ 
    [[(OCMockObject *)vc.webService verify]; 
} 

@end 
+0

"TestConfiguration"のようなシングルトンオブジェクトを持っていないと、呼び出しごとに偽の結果を取得することはできませんモックカテゴリ内で? –

+0

任意の内部構造をプロパティとして公開することができます。したがって、必要に応じてクラスを構成できます。 –

1

aryaxtからのWebServiceアンサーの上にビルドします。ここでは、別のテストで異なる結果を得るための少しトリックです。

まず、あなたが右のテストの前に、必要な答えを格納するために使用されるシングルトンオブジェクトが必要 TestConfiguration.h

#import <Foundation/Foundation.h> 
#import <objc/runtime.h> 
#import <objc/message.h> 


void MethodSwizzle(Class c, SEL orig, SEL new); 

@interface TestConfiguration : NSObject 


@property(nonatomic,strong) NSMutableDictionary *results; 

+ (TestConfiguration *)sharedInstance; 


-(void)setNextResult:(NSObject *)result 
    forCallToObject:(NSObject *)object 
       selector:(SEL)selector; 


-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector; 
@end 

TestConfiguration.m

#import "TestConfiguration.h" 


void MethodSwizzle(Class c, SEL orig, SEL new) { 
    Method origMethod = class_getInstanceMethod(c, orig); 
    Method newMethod = class_getInstanceMethod(c, new); 
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) 
     class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 
    else 
     method_exchangeImplementations(origMethod, newMethod); 
}; 

@implementation TestConfiguration 


- (id)init 
{ 
    self = [super init]; 
    if (self) { 
     self.results = [[NSMutableDictionary alloc] init]; 
    } 
    return self; 
} 

+ (TestConfiguration *)sharedInstance 
{ 
    static TestConfiguration *sharedInstance = nil; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     sharedInstance = [[TestConfiguration alloc] init]; 
     // Do any other initialisation stuff here 
    }); 
    return sharedInstance; 
} 


-(void)setNextResult:(NSObject *)result 
    forCallToObject:(NSObject *)object 
      selector:(SEL)selector 
{ 
    NSString *className = NSStringFromClass([object class]); 
    NSString *selectorName = NSStringFromSelector(selector); 

    [self.results setObject:result 
        forKey:[[className stringByAppendingString:@":"] stringByAppendingString:selectorName]]; 
} 

-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector 
{ 
    NSString *className = NSStringFromClass([object class]); 
    NSString *selectorName = NSStringFromSelector(selector); 

    return [self.results objectForKey:[[className stringByAppendingString:@":"] stringByAppendingString:selectorName]]; 

} 



@end 

次に、モックメソッドを定義するための "Mock"カテゴリを定義します。

#import "MyWebService+Mock.h" 
#import "TestConfiguration.h" 

@implementation MyWebService (Mock) 


-(void)mockFetchEntityWithId:(NSNumber *)entityId 
          success:(void (^)(Entity *entity))success 
          failure:(void (^)(NSError *error))failure 
{ 

    Entity *response = (Entity *)[[TestConfiguration sharedInstance] getResultForCallToObject:self selector:@selector(fetchEntityWithId:success:failure:)]; 

    if (response == nil) 
    { 
     failure([NSError errorWithDomain:@"entity not found" code:1 userInfo:nil]); 
    } 
    else{ 
     success(response); 
    } 
} 

@end 

そして最後に、テストそのものには、セットアップでモックメソッドをスウィズルだろう、とコール

MyServiceTest.m

- (void)setUp 
{ 
    [super setUp]; 

    //swizzle webservice method call to mock object call 
    MethodSwizzle([MyWebService class], @selector(fetchEntityWithId:success:failure:), @selector(mockFetchEntityWithId:success:failure:)); 
} 

- (void)testWSMockedEntity 
{ 
    /* mock an entity response from the server */ 
    [[TestConfiguration sharedInstance] setNextResult:[Entity entityWithId:1] 
             forCallToObject:[MyWebService sharedInstance] 
               selector:@selector(fetchEntityWithId:success:failure:)]; 

    // now perform the call. You should be able to call STAssert in the blocks directly, since the success/error block should now be called completely synchronously. 
} 

備考前に、各テストで期待される答えを定義します:私の例では、TestConfigurationはclass/selectorをobject/selectorの代わりにキーとして使います。つまり、クラスのすべてのオブジェクトがセレクタに対して同じ回答を使用します。 Webサービスはしばしばシングルトンであるため、これがあなたのケースです。しかし、クラスの代わりにobjetのメモリアドレスを使用してオブジェクト/セレクタに変更する必要があります