Pergunta

Muitas vezes eu encontrar em meu iPhone testes de unidade Objective-C que eu quero esboço sobre um método de classe, por exemplo, de NSURLConnection + sendSynchronousRequest: returningResponse: erro:. método

Exemplo simplificado:

- (void)testClassMock
{
    id mock = [OCMockObject mockForClass:[NSURLConnection class]];
    [[[mock stub] andReturn:nil] sendSynchronousRequest:nil returningResponse:nil error:nil];
}

Ao executar isso, eu recebo:

Test Case '-[WorklistTest testClassMock]' started.
Unknown.m:0: error: -[WorklistTest testClassMock] : *** -[NSProxy doesNotRecognizeSelector:sendSynchronousRequest:returningResponse:error:] called!
Test Case '-[WorklistTest testClassMock]' failed (0.000 seconds).

Eu tive um momento muito difícil encontrar qualquer documentação sobre isso, mas eu supor que métodos de classe não são suportados pelo OCMock.

Eu encontrei esta dica depois de muito Googling. Ele funciona, mas é muito complicado: http://thom.org.uk/ 2009/05/09 / zombando-class-métodos-em-objetivo-c /

Existe uma maneira de fazer isso dentro OCMock? Ou alguém pode pensar em uma categoria de objeto inteligente OCMock que poderia ser escrito para realizar esse tipo de coisa?

Foi útil?

Solução

Vindo do mundo do Ruby, eu entendo exatamente o que você está tentando realizar. Aparentemente, você foi, literalmente, três horas antes de mim tentando fazer exatamente a mesma coisa hoje (coisa fuso horário: -?).

De qualquer forma, eu Believe que este não é suportado no modo como se poderia desejar em OCMock porque arrancando um método de classe necessidades para, literalmente, chegar à classe e muda a sua implementação do método independentemente de quando, onde, ou que chama o método. Isto está em contraste com o que OCMock parece estar a fazer o que é fornecer-lhe um objeto de proxy que você manipular e de outra forma operar directamente e em vez de um objeto "real" da classe especificada.

Por exemplo, parece razoável querer stub NSURLConnection + sendSynchronousRequest: returningResponse: Erro: método. No entanto, é comum que o uso deste chamada dentro de nosso código é um pouco enterrado, tornando assim muito difíceis de parametrizar-lo e trocar em um objeto de simulação para a classe NSURLConnection.

Por isso, acho que o "método swizzling" aproximar você descobriu, embora não seja sexy, é exatamente o que você quer fazer para arrancar métodos de classe. Para dizê-lo de muito complicado parece extrema - como sobre nós concordamos que é "deselegante" e talvez não tão conveniente como OCMock torna a vida por nós. No entanto, é uma solução bastante concisa para o problema.

Outras dicas

Atualização para OCMock 3

OCMock modernizou a sua sintaxe para apoiar método de classe stubbing:

id classMock = OCMClassMock([SomeClass class]);
OCMStub(ClassMethod([classMock aMethod])).andReturn(aValue);

Atualizar

OCMock agora suporta método de classe apagando da caixa. O código do OP agora deve funcionar como publicado. Se não houver um método de instância com o mesmo nome que o método de classe, a sintaxe é:

[[[[mock stub] classMethod] andReturn:aValue] aMethod]

Características do OCMock .

Resposta Original

O código de exemplo seguinte resposta de Barry Wark.

A classe fake, apenas arrancar connectionWithRequest: delegado:

@interface FakeNSURLConnection : NSURLConnection
+ (id)sharedInstance;
+ (void)setSharedInstance:(id)sharedInstance;
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate;
- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate;
@end
@implementation FakeNSURLConnection
static id _sharedInstance;
+ (id)sharedInstance { if (!_sharedInstance) { _sharedInstance = [self init]; } return _sharedInstance; }
+ (void)setSharedInstance:(id)sharedInstance { _sharedInstance = sharedInstance; }
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate {
    return [FakeNSURLConnection.sharedInstance connectionWithRequest:request delegate:delegate];
}
- (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id<NSURLConnectionDelegate>)delegate { return nil; }
@end

Comutação de e para o mock:

{
    ...
    // Create the mock and swap it in
    id nsurlConnectionMock = [OCMockObject niceMockForClass:FakeNSURLConnection.class];
    [FakeNSURLConnection setSharedInstance:nsurlConnectionMock];
    Method urlOriginalMethod = class_getClassMethod(NSURLConnection.class, @selector(connectionWithRequest:delegate:));
    Method urlNewMethod = class_getClassMethod(FakeNSURLConnection.class, @selector(connectionWithRequest:delegate:));
    method_exchangeImplementations(urlOriginalMethod, urlNewMethod);

    [[nsurlConnectionMock expect] connectionWithRequest:OCMOCK_ANY delegate:OCMOCK_ANY];

    ...
    // Make the call which will do the connectionWithRequest:delegate call
    ...

    // Verify
    [nsurlConnectionMock verify];

    // Unmock
    method_exchangeImplementations(urlNewMethod, urlOriginalMethod);
}

Aqui é um bom 'essência' com uma implementação swizzle para métodos de classe: https://gist.github.com / 314009

Se você modificar o seu método em teste para tirar um parâmetro que injeta a classe do NSURLConnection, então é relativamente fácil de passar em um mock que responde ao selector dada (você pode ter que criar uma classe manequim no seu módulo de teste que tem o selector como um método de instância e simulada que classe). Sem essa injeção, você está usando um método de classe, essencialmente usando NSURLConnection (a classe) como um singleton e, portanto, ter caído na anti-padrão de uso de objetos únicos e a capacidade de teste de seu código sofreu.

Link para o blogpost na questão e ReFux essência me inspirou a vir para cima com bloco habilitado implementação de suas idéias: https : //gist.github.com/1038034

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top