使用RACSubject对RACSignal的简单用法进行单元测试

时间:2014-02-04 23:28:59

标签: unit-testing reactive-cocoa

(我可能会以完全错误的方式使用它,所以请随意挑战这篇文章的前提。)

我有一个小RACTest appsound familiar?),我正在尝试进行单元测试。我想测试MPSTicker,它是最基于ReactiveCocoa的组件之一。如果累积标志设置为YES,它有一个每秒发送一次累积值的信号。我added an initializer为其递增信号采取自定义信号,而不是仅基于定时器。

我想对MPSTicker的几个行为进行单元测试:

  • 当启用累加并且输入递增信号发送新值时,验证其累积信号是否正确递增(即单调增加)。
  • 当输入信号发送值时,验证它是否发送相同的值(而不是递增的值)。

我添加了a test that uses the built-in timer to test the first increment,它按照我的预期工作(虽然我正在寻求改进愚蠢的RACSequence初始化的建议,但我得到的信号是我想要的@(1)值。)

我很难确定哪些输入信号可以提供给MPSTicker,我可以手动发送值。我正在设想一个像以下的测试:

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

我尝试使用RACSubject,因此我可以使用sendNext:按照我认为合适的方式推送值,但这并不像我预期的那样有效。这是两个破碎的测试:

- (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)];
}

testManualTimerTheFirst中,我从未发现controlledSignal sendNext: subscribeNext:阻止任何价值进入testManualTimerTheSecond阻止。

asynchronousFirstOrDefault:中,我尝试使用asynchronousFirstOrDefault:调用从信号中获取第一个值,然后手动向我的主题发送一个值,但该值未通过,并且测试{{1}}超时时失败。

我在这里缺少什么?

2 个答案:

答案 0 :(得分:6)

这可能无法准确回答您的问题,但它可能会为您提供有关如何有效测试信号的见解。到目前为止,我自己使用了两种方法:

XCTestCase和TRVSMonitor

TRVSMonitor是一个小实用程序,它会在您运行断言时为您暂停当前线程。例如:

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];

正如您所看到的,我告诉监视器在我订阅之后立即等待并发信号以在subscribeNext和错误块结束时停止等待以使其继续执行(因此其他测试也可以运行)。这种方法具有不依赖静态超时的好处,因此您的代码可以根据需要运行。

使用CocoaPods,您可以轻松地将TRVSMonitor添加到您的项目中:

pod "TRVSMonitor", "~> 0.0.3"

Specta&amp; Expecta

Specta是一个BDD / TDD(行为驱动/测试驱动)测试框架。 Expecta是一个提供更方便的断言匹配器的框架。它内置了对异步测试的支持。它使您能够使用ReactiveCocoa编写更多描述性测试,如下所示:

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();
    }];
});

请注意使用 ^ AsyncBlock {。仅使用 ^ {就意味着进行同步测试。

这里调用done()函数来表示异步测试的结束。我相信Specta在内部使用了10秒的超时。

使用CocoaPods,您可以轻松添加Expecta&amp; Specta:

pod "Expecta", "~> 0.2.3"
pod "Specta", "~> 0.2.1"

答案 1 :(得分:0)

请参阅此问题:https://stackoverflow.com/a/19127547/420594

XCAsyncTestCase有一些额外的功能可以允许异步测试用例。

另外,我还没有深入研究过,但ReactiveCocoaTests对你有什么兴趣?乍一看,他们似乎正在使用Expecta。