Pruebas unitarias en iOS con OCMock

jueves, 26 de febrero de 2015


Ya había escrito un artículo sobre pruebas unitarias con moq para .Net y pruebas unitarias con Mockito para Android, ahora vamos a ver como podemos hacer pruebas unitarias con mocks en iOS utilizando OCMock, que es una de las liberías que existen para iOS para crear mocks. Existen otras como la versión Mockito para iOS, pero OCMock lleva bastante más tiempo.

Introducción a OCmock

OCMock es un libería que nos va a permitir crear objetos dobles, pudiendo así realizar pruebas unitarias de objetos con dependencias de forma clara y simple.

Podemos añadir OCMock como una libería estática descargandola de OCMock"la página de descargas o mediante cocoapods.

Ejemplo

Vamos a realizar el mismo ejemplo que habíamos visto en los anteriores artículos para .Net y Android para ver que los conceptos se aplican de la misma forma en todas las plataformas.

Vamos a tener un BlogService con un método publishPost y va a tener dos dependencías que se le inyectan en el inicializador, un Logger y un HtmlValidator. En caso de validar correctamente el html a publicar, escribe en el log y devuelve true, si el html es incorrecto escribe en el log el error y devuelve un NSError.

Veamos los componentes

//Protocol dependencies
@protocol XURLogger

-(void) logWithMessage(NSString *)message;
@end

@protocol XURHtmlValidator

-(BOOL) isValidHtml(NSString *)html;
@end

//Class with protocol dependencies
@interface XURBlogService

-(instanceType) initWithLogger(NSObject<Logger> *)logger htmlValidator(NSObject<HtmlValidator> *) htmlValidator; 
-(BOOL) publishPostWithHtml:(NSString *)html error(NSError **)error ;
@end

@implementation BlogService

-(instanceType) initWithLogger(NSObject<Logger> *)logger htmlValidator(NSObject<HtmlValidator> *) htmlValidator{
    if (self = [super init]) {
        _logger = logger;
        _htmlValidator = htmlValidator;
    }
    return self;
}

-(BOOL) publishPostWithThml(NSString *) html error(NSError **)error{ 
    if ([htmlValidator isValidHtml:html]){
        NSLog(@"Post has published");

        return YES;
    }
    else{
    
        if (error != nil){

            NSMutableDictionary* details = [NSMutableDictionary dictionary];
            [details setValue:@"invalid html" forKey:NSLocalizedDescriptionKey];

            *error = [NSError errorWithDomain:@"com.xurxo.exampledomain" code:200 userInfo:details];
        }

        return NO;
    }
}
@end

Testeando con un Dummy

Una primera prueba unitaria es simplemente crearnos las dependencias sin implementación, a modo de objetos dummy para verificar que no hay ningún problema al crear el servicio de blog.

- (void)testBlogServiceTest {
        id loggerDummy = OCMProtocolMock(@protocol(XURLogger));
        id htmlValidatorDummy = OCMProtocolMock(@protocol(XURHtmlValidator));

        XURBlogService *blog = [[XURBlogService alloc] initWithLogger:loggerDummy htmlValidator:htmlValidatorDummy]];

        XCTAssertNotNil(blog);
}    

Testeando con un Stub

La siguiente prueba unitaria es comprobar que cuando le pasamos un html incorrecto, el servicio de blog nos devuelve un NSError. Preparamos un stub para el protocolo HtmlValidator que siempre devuelva false en el método isValid y en el test verificamos que nos devuelve un NSError en este caso.

- (void)testPublishPostShouldCreateErrorForNotValidHtml {

    id loggerDummy = OCMProtocolMock(@protocol(XURLogger));
    id htmlValidatorStub = OCMProtocolMock(@protocol(XURHtmlValidator));

    OCMStub([htmlValidatorStub isValidHtml:[OCMArg any]]).andReturn(NO);

    XURBlogService *blog = [[XURBlogService alloc] initWithLogger:loggerDummy htmlValidator:htmlValidatorStub]];

    NSError *error;

    BOOL success = [XURBlogService publishPostWithHtml:@"invalid html text" error:&error];

    XCTAssertFalse(success);
    XCTAssertNotNil(error);
    XCTAssertEqualObjects([error localizedDescription], @"invalid html", @"Not expected localized description error");
}

Testeando con un Mock

En la siguiente prueba vamos a verificar el caso contrario, que todo va bien y no se produce ninguna excepción, además vamos a verificar que servicio de blog realiza una llamada a nuestro objeto doble HtmlValidator, lo que lo convierte en un mock.

    - (void)testPublishPostShouldWork {

    id loggerDummy = OCMProtocolMock(@protocol(XURLogger));
    id htmlValidatorMock = OCMProtocolMock(@protocol(XURHtmlValidator));

    OCMStub([htmlValidatorMock isValidHtml:[OCMArg any]]).andReturn(YES);

    XURBlogService *blog = [[XURBlogService alloc] initWithLogger:loggerDummy htmlValidator:htmlValidatorStub]];

    NSError *error;

    BOOL success = [XURBlogService publishPostWithHtml:@"valid html text" error:&error];

    XCTAssertTrue(success);
    XCTAssertNil(error);
    OCMVerify([htmlValidatorMock isValidHtml:[OCMArg any]]);
}


En el método verify estamos verificando que se ha invocado la función isValid y con OCMArg any indicamos que el parámero da igual para la verificación, puede ser cualquier valor. Si hubieramos especificado un html concreto lo que estariamos verificando es que se haya llamado a la función isValid con un parámetro concreto.

Libros relacionados

Test-Driven iOS Development (Developer's Library)

iOS Programming: The Big Nerd Ranch Guide

Objective-C Programming: The Big Nerd Ranch Guide

No hay comentarios:

Publicar un comentario