Tests unitaires - OHHTTPStubs
Hello,
Je me pose pleins de questions en ce moment sur comment bien tester mon projet. Je dois intégrer des tests fonctionnels et unitaires. En ce moment je suis entrain de tester mes requêtes services web. J'utilise OHHTTPStubs pour "stuber" mes requêtes réseaux. Voici un bout de mon code du l'API controller :
@interface SGDataAPIController : AFHTTPSessionManager
+ (SGDataAPIController *)sharedManager;
- (void)fetchCustomerListWithName:(NSString *)name completionHandler:(void (^) (NSArray *customers, NSError *error))handler;
@end
@implementation SGDataAPIController
+ (SGDataAPIController *)sharedManager {
static SGDataAPIController *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURL *baseUrl = [NSURL URLWithString:kBaseUrlString];
manager = [[SGDataAPIController alloc] initWithBaseURL:baseUrl sessionConfiguration:sessionConfiguration];
[manager setResponseSerializer:[AFJSONResponseSerializer serializer]];
manager.completionQueue = dispatch_queue_create("com.myCompany.completionQueue", DISPATCH_QUEUE_SERIAL);
});
return manager;
}
....
#pragma mark - Customer requests
- (void)fetchCustomerListWithName:(NSString *)name
completionHandler:(void (^) (NSArray *customers, NSError *error))handler {
NSDictionary *params = @{@customerName:name};
SGRequestCompletion completion = ^(SGRequestResult *result, BOOL accessFailure) {
.....
NSArray *customers = [SGCustomerBuilder customerListWithDicts:result.payloadArray];
...
dispatch_async(dispatch_get_main_queue(), ^{
handler(customers, NO);
});
};
[self GET:kCustomerUrlString parameters:params
success:[SGDataAPIController jsonSuccessBlockWithCompletionBlock:completion]
failure:[SGDataAPIController jsonFailureBlockWithCompletionBlock:completion]];
}
Maintenant mon but est de tester cette méthode d'une manière complète.
Voila ma classe de test :
@interface SGDataAPIControllerTests : XCTestCase
@end
@implementation SGDataAPIControllerTests
- (void)setUp {
[super setUp];
}
- (void)tearDown {
[OHHTTPStubs removeAllStubs];
[super tearDown];
}
- (void)testFetchCustomerList {
XCTestExpectation *expectation = [self expectationWithDescription:@Fetch customer lisst api request];
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
// temporaire, je doit tester sur l'url de l'API
return YES;
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
NSArray *customers = @[;@{@name : @customer1}];
return [OHHTTPStubsResponse responseWithJSONObject:customers statusCode:200 headers:nil];
}];
SGDataAPIController *manager = [SGDataAPIController sharedManager];
[manager fetchCustomerListWithName:@test completionHandler:^(NSArray *customers, NSError *error) {
[expectation fulfill];
XCTAssertNil([error description]);
XCTAssertTrue([customers isKindOfClass:NSArray.class]);
SGCustomer *customer = [customers firstObject];
XCTAssertNotNil(customer);
XCTAssertEqualObjects(customer.name, @customer1);
}];
[self waitForExpectationsWithTimeout:5
handler:^(NSError *error) {
if (error) {
XCTFail(@timeout error: %@", error);
}
}];
}
Est-ce que le test de ma méthode est complet ?
Je vois plein d'autre cas :
Est-ce que je dois tester par exemple si mon serveur me renvoie un JSON non valide, ma méthode se comporte bien ? si le serveur me renvoie un dictionnaire au lieux d'un array, si j'ai pas de connexion internet ? timeout,.....?
C'est quand même beaucoup de tout tester !!
Comment vous faites svp ? Merci.
@Aligator : En utilisant ta librairie OHHTTPStubs, j'ai remarqué que y a un pod qui permet de nettoyer les "stubs" entre chaque test. https://github.com/1aurabrown/XCTest-OHHTTPStubSuiteCleanUp. Dans la documentation y'a la méthode removeAllStubs qu'il faut appeler dans le tearDown. Je suppose que c'est suffisant, non ?
Réponses
Donc oui dans l'idéal il faut tout tester. Car si tu ne testes pas le cas où tu n'as pas de connexion internet, et qu'il se trouve que justement tu gères mal ce cas dans ton code (ou que aujourd'hui ça le gère bien, mais que dans 3 mois tu fais un gros refactor et que dans l'opération tu oublies de remettre la gestion de ce cas-là ...), alors si tu ne le testes pas tu ne réaliseras pas qu'il ne marche plus, et que potentiellement il fait crasher ton application, ou qu'il bloque ton application (freeze) ou je ne sais quoi encore. Donc évidemment qu'il faut un test unitaire pour ce cas aussi, c'est tout l'intérêt d'un test unitaire justement. Si tu ne le testes pas tu ne pourras pas savoir s'il ne marche plus ou si le fonctionnel permettant de traiter ce cas non connecté n'a pas subi de régression.
Après, si toutes tes méthodes se basent sur une méthode parente qui se chargent d'envoyer une requête et de traiter la réponse, et que chaque méthode individuelle ne fait que faire varier l'URL et le block de parsing du JSON par exemple, alors tu n'as besoin d'écrire qu'une seule fois le test unitaire pour la gestion de l'absence de connexion internet, tu l'écris pour ta méthode parente commune à tous.
Ca dépend comment tu as fait ton archi en fait, mais bon j'imagine que tu as mutualisé du fonctionnel, tu peux aussi mutualiser du test unitaire commun pour plusieurs cas.
Oui, le pod de Laura (une collègue de Orta qui bosse aussi à Artsy) ne fait qu'appeler removeAllStubs dans la méthode de tearDown. Si tu regardes plus en détail son pod en fait elle a fait du swizzling introduire ça plutôt que d'utiliser l'héritage, ce qui est un peu de l'overkill pour si peu. Mais au in fine ça ne finit que par faire ce removeAllStubs à la fin de chaque test, ce qui est le point important pour éviter que tes stubs d'un test risquent d'être toujours actifs pour le test suivant et rentrent en conflit avec ce dernier.