3

I have a Kiwi spec file that looks something like this:

#import "Kiwi.h"
#import "MyCollection.h"

SPEC_BEGIN(CollectionSpec)

describe(@"Collection starting with no objects", ^{
    MyCollection *collection = [MyCollection new];

    context(@"then adding 1 object", ^{
        MyObject *object = [MyObject new];
        [collection addObject:object];
        it(@"has 1 object", ^{
            [collection shouldNotBeNil];
            [collection.objects shouldNotBeNil];
            [[theValue(collection.objects.count) should] equal:theValue(1)]; //failing test
        });

        context(@"then removing 1 object", ^{
            [collection removeObject:object];
            it(@"has 0 objects", ^{
                [[theValue(collection.objects.count) should] equal:theValue(0)]; //passing test
            });
        });
    });
});

SPEC_END

Running the spec results in one failure at this line of code [[theValue(collection.objects.count) should] equal:theValue(1)];

Here's the strange part - if I remove the whole context(@"then removing 1 object", ^{...}) block from the spec the aforementioned test passes.

This leads me to believe that the [collection removeObject:object] line is getting executed before the failing test. I have a feeling I may be misunderstanding the order that blocks are executed in.

Any suggestions would be appreciated!

Barjavel
  • 1,626
  • 3
  • 19
  • 31
  • you do not need `__block` on `collection` or `object`, since you are not assigning to either one anywhere – newacct Apr 13 '13 at 06:54
  • You're right - I have edited my question. When I change this I still see the same behaviour though. – Barjavel Apr 13 '13 at 07:40
  • i don't know much about Kiwi, but you sure `[collection shouldNotBeNil];` is correct? if `collection` is `nil` it will just be nop – Bryan Chen Apr 13 '13 at 12:55
  • `shouldNotBeNil` is a standard part of the Kiwi library, but I can see how it might look misleading. Note that it is not a normal method call - it is actually a macro that ends up adding an expectation type to the spec object. So it's not actually passing a message to a potentially nil object. You can checkout all of Kiwi's source code here if you'd like a closer look? - https://github.com/allending/Kiwi – Barjavel Apr 13 '13 at 13:49
  • That's correct, `shouldNotBeNil` is a special macro that will do the right thing. Note also that the more typical `should` and `shouldNot` macros that are used for most Kiwi expectations will implicitly set an expectation that the subject is not actually `nil` (in addition to the actual expectation you are setting up, such as `shouldEqual:something`). – Mike Mertsock Apr 14 '13 at 22:38
  • 1
    Also, you could eliminate your `shouldNotBeNil` checks and replace the count checks with the simpler: `[[collection.objects should] haveCountOf:integer]`. The `haveCountOf:` message doesn't require use of `theValue`, you pass it a scalar integer. Since the `should` will implicitly assert that `collection.objects` is not `nil` (and thus would also fail if `collection` itself is nil), it's safe to do this without a separate `shouldNotBeNil` assertion even when you are checking for a count of zero. – Mike Mertsock Apr 15 '13 at 12:22

1 Answers1

10

You are correct that [collection removeObject:object] is getting executed before the failing test. Think of Kiwi tests as operating in two passes:

  1. Setup: the code in your test file is executed from top to bottom to set up the test contexts and expectations
  2. Execution: each unit test is run, basically one per it/specify statement, with the correct setup/teardown code repeated for each test

Note that most of the code in a Kiwi test file is specified as a series of blocks sent to the Kiwi functions. Any code that doesn't follow the Kiwi block patterns, such as your code that initializes/modifies the collection variable, might thus execute at an unexpected time. In this case, all of the collection modification code is being executed during the first pass when setting up your tests, and then your tests are run.

Solution

Declare collection with a __block modifier, and use beforeEach to instantiate and modify the collection object:

describe(@"Collection starting with no objects", ^{
    __block MyCollection *collection;
    beforeEach(^{
        collection = [MyCollection new];
    });

    context(@"then adding 1 object", ^{
        beforeEach(^{
            MyObject *object = [MyObject new];
            [collection addObject:object];
        });
        it(@"has 1 object", ^{
            ...
        });

        context(@"then removing 1 object", ^{
            beforeEach(^{
                [collection removeObject:object];
            });
            it(@"has 0 objects", ^{
                ...

The beforeEach blocks tell Kiwi to specifically run the given code once per unit test, and with nested contexts, the blocks will be executed in sequence as needed. So Kiwi will do something like this:

// run beforeEach "Collection starting with no objects"
collection = [MyCollection new]
// run beforeEach "then adding 1 object"
MyObject *object = [MyObject new]
[collection addObject:object]
// execute test "has 1 object"
[collection shouldNotBeNil]
[collection.objects shouldNotBeNil]
[[theValue(collection.objects.count) should] equal:theValue(1)]

// run beforeEach "Collection starting with no objects"
collection = [MyCollection new]
// run beforeEach "then adding 1 object"
MyObject *object = [MyObject new]
[collection addObject:object]
// run beforeEach "then removing 1 object"
[collection removeObject:object]
// execute test "has 0 objects"
[[theValue(collection.objects.count) should] equal:theValue(0)]

The __block modifier will ensure that the collection object reference can be properly retained and modified through all of these block functions.

Mike Mertsock
  • 11,825
  • 7
  • 42
  • 75
  • This explanation has really helped! I now think of the `context/describe` blocks as being logical separators only, with any code needing to be in some other type of Kiwi block. Thanks for all the info - much appreciated! – Barjavel Apr 15 '13 at 19:30