Choose your unit test tool! - Specta
We have thousands of open source library which we use everyday. However, sometimes you need to choose which tool you want to use. The same is with unit tests in iOS. There are two kinds of iOS developers who practice TDD:
- Who use a native iOS unit test framework (XCTest)
- Who use a BDD-style framework
BDD Frameworks
As an iOS developer, you can choose from a variety of BDD frameworks. The most popular BDD library are Kiwi, Cedar, Specta and Quick (for Swift). I use Specta. I haven’t used Kiwi or Cedar. In future, I would like to use Quick with Swift and check how it works ;).
What is a BDD?
What I want to share in this article is to help you understand the way how tests look like with BDD-style library. It isn’t another place to study differences between TDD and BDD. In Google you can find many sources of debate. To encapsulate what we gonna need today is that BDD-style is all about syntax and naming.
Syntax and naming
XCTest file (native unit test framework integrated with Xcode) looks like any other class you write during your work day. Unfortunately it is also not readable. Especially when you have 2 or more cases to cover.
- (void)testAddingElementToEmptyList{
[self.systemUnderTests addObject:@1];
XCTAssertEqualObjects([self.systemUnderTests objectAtIndex:0], @1);
}
- (void)testAddingToNonEmptyList{
[self.systemUnderTests addObject:@0];
[self.systemUnderTests addObject:@1];
[self.systemUnderTests addObject:@2];
XCTAssertEqualObjects([self.systemUnderTests objectAtIndex:0], @0);
XCTAssertEqualObjects([self.systemUnderTests objectAtIndex:1], @1);
XCTAssertEqualObjects([self.systemUnderTests objectAtIndex:2], @2);
}
BDD frameworks heavily use blocks to express a test. Thanks to that you are able to create a more readable test with nested contexts.
describe(@"adding and receiving object form list",^{
describe(@"addObject:", ^{
context(@"list is empty",^{
it(@"should add object at index 0",^{
[systemUnderTests addObject:@0];
expect([systemUnderTests objectAtIndex:0]).to.equal(@0);
});
});
context(@"when list is not empty",^{
before(^{
[systemUnderTests addObject:@0];
[systemUnderTests addObject:@1];
});
it(@"should add object at index 2", ^{
[systemUnderTests addObject:@2];
expect([systemUnderTests objectAtIndex:2]).to.equal(@2);
});
});
});
});
describe(), context(), it() and pending() – What are they for?
At the beginning, I had problems with Specta blocks. Every of these blocks get 2 arguments. String and a block.
it()
Usage of it()
block is very intuitive. As a first argument just put a description of what you want to test. It is like a test method in XCTest framework.
it(@"should add an object as a first item"),^{
body of a test goes here.
});
describe()
The difference between describe() and context() blocks was for me really hard to understand. Both of them are made for making your test code more expressive.This is how I use describe() now:
- At the beginning of the test file
- When I want to test a specific method
describe(@"addObject:",^{ ... });
- When I want to mark part of a test related with each other.
describe(@"manipulating a body of list",^{ describe(@"addObject:",^{ ... )}; describe(@"removeObject",^{ ... )}; }); describe(@"objectAtIndex:",^{ ... });
context()
Context blocks are used to define a state of object under which method is invoked. For example, when you want to test removeObjectFromIndex:
method you should test all possibilities of passed index
argument. By all I mean: at bounds values, (like 0 and last), in bounds value (like 5 in 10 elements array) and out of bounds value like 100.
describe(@"removeObjectFromIndex:",^{
context(@"when array has 10 elements",^{
context(@"when at bounds index is passed",^{
...
});
context(@"when in bounds index is passed",^{
...
});
context(@"when out of bounds index is passed",^{
...
});
});
});
As you notice, context’s description usually start with „when” word.
pending()
Pending block is used to disable some part of tests. It is useful while refactoring your test file.
before() and after().
These are easy. before()
block will execute before every it()
block. It is used to set up a state of the class which is under a test. before
block is very important, because each test shouldn’t rely on any previous test state. after()
is very similar. It execute after ever it()
. It is used to clean up after every test.
Nested contexts. Why are they so cool?
I think the best part of Specta is that it allows us to have before()
in every context()
or describe()
block. That means we can split an initialization of dependencies to places where they are really needed. In XCTest we have only one setUp:
method. It forces us to move setting the state of the class to test method.
Expecta
Kiwi and Cedar have built-in matchers methods. Specta is just a BDD framework which provide only a describe()
/context()
syntax. Expecta is a sister library of Specta. Expecta provides matchers which are a replacement for XCTest asserts. With matchers we can simply check if variable is a kind of class, if passed value is greater than x or even if given collection is a superset of other collection. Full list of available matchers is here. Expecta is also much more readable! We can read asserts like normal english sentence. I’ve already used Expecta in this article. Lines with expect()
word are assert and matchers from Expecta.
Last word
Specta and Expecta are very good libraries to start a journey with tests in Objective-C. My thoughts are that block syntax makes our tests more readable and isolated from each other. To download a sample for this article go to my GitHub. Enjoy 😉
Question: „What library do you use to write your tests?”
PS Don’t forget to share the article 😉