RACSignal Easy-Use Group Testing with RACSubject

(Perhaps I am using this completely wrong, so feel free to dispute the sending of this message.)

I have a small RACTest application ( sound familiar? ) That I'm trying to unit test. I would like to test MPSTicker , one of the most active components of ReactiveCocoa. It has a signal that sends a value once per second, which accumulates if the accumulation flag is set to YES. I added an initializer to use my own signal for its increasing signal, not just a timer-based one.

I wanted to unit test a couple of MPSTicker behaviors:

  • Ensure that its accumulation signal increases (i.e. monotonously increases) when accumulation is turned on and the input increment signal sends a new value.
  • Make sure it sends the same value (and not an incremented value) when the input sends the value.

I added a test that uses the built-in timer to check for the first increment , and it works as I expected (although I am looking for advice on improving the dull initialization of RACSequence, which I did to get the signal with the @(1) value that I wanted. )

I had a very difficult time figuring out which input signal I can provide to the MPSTicker, which I can manually send values ​​to. I foresaw a test like:

 <set up ticker> <send a tick value> <verify accumulated value is 1> <send another value> <verify accumulated value is 2> 

I tried using RACSubject , so I can use sendNext: to enter the values ​​as I see fit, but it doesn't work as I expect. Here are two broken tests:

 - (void)testManualTimerTheFirst { // Create a custom tick with one value to send. RACSubject *controlledSignal = [RACSubject subject]; MPSTicker *ticker = [[MPSTicker alloc] initWithTickSource:controlledSignal]; [ticker.accumulateSignal subscribeNext:^(id x) { NSLog(@"%s value is %@", __func__, x); }]; [controlledSignal sendNext:@(2)]; } - (void)testManualTimerTheSecond { // Create a custom tick with one value to send. RACSubject *controlledSignal = [RACSubject subject]; MPSTicker *ticker = [[MPSTicker alloc] initWithTickSource:controlledSignal]; BOOL success = NO; NSError *error = nil; id value = [ticker.accumulateSignal asynchronousFirstOrDefault:nil success:&success error:&error]; if (!success) { XCTAssertTrue(success, @"Signal failed to return a value. Error: %@", error); } else { XCTAssertNotNil(value, @"Signal returned a nil value."); XCTAssertEqualObjects(@(1), value, @"Signal returned an unexpected value."); } // Send a value. [controlledSignal sendNext:@(1)]; } 

In testManualTimerTheFirst I never see any value from controlledSignal sendNext: to my subscribeNext: block.

In testManualTimerTheSecond I tried to use the asynchronousFirstOrDefault: call to get the first value from the signal, then manually sent the value according to my question, but the value did not work, and the test failed when asynchronousFirstOrDefault: timeout.

What am I missing here?

0
source share
2 answers

This may not answer your question exactly, but it may give you an idea of ​​how to effectively test your signals. So far I have used 2 approaches:

XCTestCase and TRVSMonitor

TRVSMonitor is a small utility that pauses the current thread for you while you execute your statements. For instance:

 TRVSMonitor *monitor = [TRVSMonitor monitor]; [[[self.service searchPodcastsWithTerm:@"security now"] collect] subscribeNext:^(NSArray *results) { XCTAssertTrue([results count] > 0, @"Results count should be > 0"; [monitor signal]; } error:^(NSError *error) { XCTFail(@"%@", error); [monitor signal]; }]; [monitor wait]; 

As you can see, I suggest that the monitor wait immediately after I subscribe and signal it, to stop waiting at the end of subscribeNext and error blocks to continue its execution (so that other tests can also run). This approach has the advantage of not relying on a static timeout , so your code can work as long as it needs to.

Using CocoaPods, you can easily add TRVSMonitor to your project:

 pod "TRVSMonitor", "~> 0.0.3" 

Specta and Expecta strong>

Specta is a BDD / TDD (behavior / test driven) test environment. Expecta is a framework that provides more convenient matches for approval. It has built-in support for asynchronous tests. This allows you to write more descriptive tests using ReactiveCocoa, for example:

 it(@"should return a valid image, with cache state 'new'", ^AsyncBlock { [[cache imageForURL:[NSURL URLWithString:SECURITY_NOW_ARTWORK_URL]] subscribeNext:^(UIImage *image) { expect(image).notTo.beNil(); expect(image.cacheState).to.equal(JPImageCacheStateNew); } error:^(NSError *error) { XCTFail(@"%@", error); } completed:^{ done(); }]; }); 

Note the use of ^ AsyncBlock { . Using just ^ { would imply a synchronous test.

Here you call the done () function to signal the completion of the asynchronous test. I believe Specta uses an internal timeout of 10 seconds.

Using CocoaPods, you can easily add Expecta and Specta:

 pod "Expecta", "~> 0.2.3" pod "Specta", "~> 0.2.1" 
+6
source

See this question: fooobar.com/questions/446914 / ...

XCAsyncTestCase has some additional features for asynchronous test cases.

In addition, I have not looked at it in depth yet, but could ReactiveCocoaTests be of any interest to you? At first glance, they seem to use Expecta.

0
source

All Articles