Fixed test flakiness due to a race condition with RCTRedBox

Summary:
The test runner relied on checking the current error message in the RCTRedbox, which is a singleton (yay, shared mutable state!). This lead to some tests that worked individually but failed when run together due to error messages in RCTRedBox left over from previous tests.

I've replaced the call to -[RCTRedBox currentErrorMessage] by injecting a custom logging function to intercept the errors at source, which is a much more reliable solution.
This commit is contained in:
Nick Lockwood 2015-08-19 03:18:04 -07:00
parent b18d73b568
commit 26c4be62c8

View File

@ -11,7 +11,7 @@
#import "FBSnapshotTestController.h" #import "FBSnapshotTestController.h"
#import "RCTAssert.h" #import "RCTAssert.h"
#import "RCTRedBox.h" #import "RCTLog.h"
#import "RCTRootView.h" #import "RCTRootView.h"
#import "RCTTestModule.h" #import "RCTTestModule.h"
#import "RCTUtils.h" #import "RCTUtils.h"
@ -83,6 +83,13 @@ RCT_NOT_IMPLEMENTED(-init)
- (void)runTest:(SEL)test module:(NSString *)moduleName - (void)runTest:(SEL)test module:(NSString *)moduleName
initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
{ {
__block NSString *error = nil;
RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
error = message;
}
});
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:_moduleProvider moduleProvider:_moduleProvider
launchOptions:nil]; launchOptions:nil];
@ -102,20 +109,19 @@ RCT_NOT_IMPLEMENTED(-init)
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
while ([date timeIntervalSinceNow] > 0 && testModule.status == RCTTestStatusPending && error == nil) { while ([date timeIntervalSinceNow] > 0 && testModule.status == RCTTestStatusPending && error == nil) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
error = [[RCTRedBox sharedInstance] currentErrorMessage];
} }
[rootView removeFromSuperview]; [rootView removeFromSuperview];
RCTSetLogFunction(RCTDefaultLogFunction);
NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) { NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"]; return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"];
}]]; }]];
RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews); RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews);
[[RCTRedBox sharedInstance] dismiss];
if (expectErrorBlock) { if (expectErrorBlock) {
RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched."); RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched.");
} else { } else {