{"id":7033,"date":"2014-07-04T18:56:11","date_gmt":"2014-07-04T15:56:11","guid":{"rendered":"http:\/\/railsware.com\/blog\/?p=7033"},"modified":"2021-08-16T11:48:49","modified_gmt":"2021-08-16T08:48:49","slug":"bdd-style-testing-in-swift-with-sleipnir","status":"publish","type":"post","link":"https:\/\/railsware.com\/blog\/bdd-style-testing-in-swift-with-sleipnir\/","title":{"rendered":"BDD-style testing in Swift with Sleipnir"},"content":{"rendered":"\n<h3 class=\"wp-block-heading\">Introduction<\/h3>\n\n\n\n<p>In Objective-C people use different frameworks to write BDD-style tests for their code. Some of them are:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/github.com\/pivotal\/cedar\" target=\"_blank\" rel=\"noreferrer noopener\">Cedar<\/a><\/li><li><a href=\"https:\/\/github.com\/kiwi-bdd\/Kiwi\" target=\"_blank\" rel=\"noreferrer noopener\">Kiwi<\/a><\/li><li><a href=\"https:\/\/github.com\/specta\/specta\" target=\"_blank\" rel=\"noreferrer noopener\">Specta<\/a><\/li><\/ul>\n\n\n\n<p>With the inroduction of Swift we&#8217;ve decided to create BDD-style testing framework in pure Swift.<br>After a couple weeks of implementation we are making the first public version of the framework called <a href=\"https:\/\/github.com\/railsware\/Sleipnir\" target=\"_blank\" rel=\"noreferrer noopener\">Sleipnir<\/a>.<br>Sleipnir is highly inspired by <a href=\"https:\/\/github.com\/pivotal\/cedar\">Cedar<\/a> and allows you to write BDD-style tests in Swift:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class SampleSpec : SleipnirSpec {\n    var spec : () = describe(\"Horse\") {\n        context(\"usual\") {\n            it(\"is not awesome\") {\n                let usualHorse = UsualHorse()\n                expect(usualHorse.legsCount).to(equal(4))\n                expect(usualHorse.isAwesome()).to(beFalse())\n            }\n        }\n        \n        context(\"Sleipnir\") {\n            it(\"is awesome\") {\n                let sleipnirHorse = Sleipnir()\n                expect(sleipnirHorse.legsCount).to(equal(8))\n                expect(sleipnirHorse.isAwesome()).to(beTrue())\n            }\n        }\n    }\n}\n<\/pre>\n\n\n\n<!--more-->\n\n\n\n<h3 class=\"wp-block-heading\">Core principles of Sleipnir<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li>Sleipnir is not dependent of <code>NSObject<\/code>, it is pure Swift BDD testing framework<\/li><li>Sleipnir is not using <code>XCTest<\/code><\/li><li>Sleipnir has nice command line output and support for custom test reporters<\/li><li>Other features, like seeded random tests invocation, focused and excluded examples\/groups, etc.<\/li><\/ul>\n\n\n\n<p>We&#8217;ve found some other alternatives for Swift BDD testing, like <a href=\"https:\/\/github.com\/modocache\/Quick\" target=\"_blank\" rel=\"noreferrer noopener\">Quick<\/a>. Choosing between different solutions is a matter of preference.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Usage sample<\/h3>\n\n\n\n<p>Let&#8217;s define two classes <code>Book<\/code> and <code>Library<\/code> and write tests for them.<br><code>Book<\/code> contains information about an author and a title of the book.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted toolbar:1 lang:swift decode:true\">class Book {    \n    var title: String\n    var author: String\n    \n    init(title: String, author: String) {\n        self.title = title\n        self.author = author\n    }   \n}\n<\/pre>\n\n\n\n<p><code>Library<\/code> is a simple collection of books.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted toolbar:1 lang:swift decode:true\">class Library {  \n    var books: Book[]\n    \n    init() {\n        self.books = Book[]()\n    }\n    \n    func addBook(book: Book) {\n        books.append(book)\n    }\n    \n    func removeLastBook() {\n        books.removeLast()\n    }\n    \n    func clear() {\n        books.removeAll()\n    }\n    \n    func size() -&gt; Int {\n        return books.count\n    }\n    \n    func hasBooks() -&gt; Bool {\n        return size() &gt; 0\n    }\n    \n    func filterBy(#author: String) -&gt; Book[] {\n        return books.filter { $0.author == author }\n    }\n    \n    func filterBy(#title: String) -&gt; Book[] {\n        return books.filter { !$0.title.rangeOfString(title).isEmpty }\n    }\n}\n<\/pre>\n\n\n\n<p>First let&#8217;s test that instances of <code>Book<\/code> are initialized correctly:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted toolbar:1 lang:swift decode:true\">class LibrarySpec : SleipnirSpec {\n    \n    var book : () = context(\"Book\") {\n        \n        var swiftBook: Book?\n        beforeAll {\n            swiftBook = Book(title: \"Introduction to Swift\", author: \"Apple Inc.\")\n        }\n        \n        it(\"has title\") {\n            expect(swiftBook!.title).to(equal(\"Introduction to Swift\"))\n        }\n       \n        it(\"has author\") {\n            expect(swiftBook!.author).to(equal(\"Apple Inc.\"))\n        }\n    }\n}\n<\/pre>\n\n\n\n<p>We&#8217;ve created a <code>LibrarySpec<\/code> class which inherits from <code>SleipnirSpec<\/code>. It has a main context and two examples which check properties of a created <code>Book<\/code> instance.<\/p>\n\n\n\n<p>An instance of <code>Book<\/code> is created in <code>beforeAll{ }<\/code> block.<\/p>\n\n\n\n<p>Sleipnir supports several blocks for specs initialization: <code>beforeAll<\/code>, <code>afterAll<\/code>, <code>beforeEach<\/code> and <code>afterEach<\/code>.<br>All the top-level example groups in a spec should be assigned to a variable in order to evaluate:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">var book : () = context(\"Book\") {  }\n<\/pre>\n\n\n\n<p>Now let&#8217;s test a behaviour of a <code>Library<\/code> class:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted toolbar:1 lang:swift decode:true\">class LibrarySpec : SleipnirSpec {\n    \n    ...\n\n    var library : () = context(\"Library\") {\n        \n        var swiftLibrary: Library?\n        beforeAll {\n            swiftLibrary = Library()\n        }\n        \n        afterAll {\n            swiftLibrary = nil\n        }\n        \n        describe(\"empty\") {\n            it(\"has no books\") {\n                expect(swiftLibrary!.hasBooks()).to(beFalse())\n            }\n        }\n        \n        describe(\"with books\") {\n            \n            beforeEach {\n                swiftLibrary!.addBook(Book(title: \"Introduction to Swift\", author: \"Apple Inc.\"))\n                swiftLibrary!.addBook(Book(title: \"Using Swift with Cocoa\", author: \"Apple Inc.\"))\n                swiftLibrary!.addBook(Book(title: \"Swift tutorials\", author: \"John Doe\"))\n                swiftLibrary!.addBook(Book(title: \"Programming iOS with Swift\", author: \"Vladimir Swiftin\"))\n            }\n            \n            afterEach {\n                swiftLibrary!.clear()\n            }\n            \n            it(\"is not empty\") {\n                expect(swiftLibrary!.hasBooks()).to(beTrue())\n            }\n            \n            it(\"has correct number of books\") {\n                expect(swiftLibrary!.size()).to(equal(4))\n                swiftLibrary!.removeLastBook()\n                expect(swiftLibrary!.size()).to(equal(3))\n            }\n            \n            describe(\"filters books\") {\n                it(\"by author\") {\n                    expect(swiftLibrary!.filterBy(author: \"Apple Inc.\").count).to(equal(2))\n                }\n                \n                it(\"by title\") {\n                    expect(swiftLibrary!.filterBy(title: \"tutorials\").count).to(equal(1))\n                }\n            }\n        }\n    }\n}\n<\/pre>\n\n\n\n<p>Running those specs will produce the following command line output:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Running With Random Seed: 657464010\n\n.......\n\n\nFinished in 0.0091 seconds\n\n7 examples, 0 failures\n<\/pre>\n\n\n\n<p>In case of a failed example you will see a detailed information about the failure including file and line number:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Running With Random Seed: 2027508247\n\n..F....\n\nFAILURE Library with books has correct number of books:\n\/Users\/atermenji\/Coding\/objc\/Sleipnir\/Sample\/LibrarySpec.swift:64 Expected 3 to equal [2]\n\n\nFinished in 0.0043 seconds\n\n7 examples, 1 failures\n<\/pre>\n\n\n\n<p>As you can see we&#8217;ve tested the behaviour of a <code>Library<\/code> class using simple expectations and matchers.<br>Sleipnir currently supports only three matchers: <code>equal<\/code>, <code>beTrue<\/code> and <code>beFalse<\/code>, but more of them will be added soon.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s next<\/h3>\n\n\n\n<p>Since this is a first public release, a lot of features are not yet implemented. We have a roadmap for the nearest future which includes:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Distribution as a framework<\/li><li>Pending examples support<\/li><li>Focused and excluded examples\/groups support implementation<\/li><li>XCode templates<\/li><li>Shared examples support<\/li><li><code>should<\/code> syntax support<\/li><li>Specs for Sleipnir using Sleipnir<\/li><li>Wiki documentation<\/li><li>More matchers, including:\n<ul>\n<li><code>beNil<\/code><\/li>\n<li><code>beGreaterThan<\/code>, <code>beLessThan<\/code>, <code>beInRangeOf<\/code><\/li>\n<li>asynchronous matchers (<code>will<\/code>, <code>willNot<\/code>, <code>after<\/code>)<\/li>\n<li>matchers on collections\/strings (<code>contains<\/code>, <code>haveCount<\/code>, <code>beginWith<\/code>, <code>endWith<\/code>, etc.)<\/li>\n<\/ul>\n<\/li><\/ul>\n\n\n\n<p>You may post your ideas or issues to the <a href=\"https:\/\/github.com\/railsware\/Sleipnir\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Sleipnir repo<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction In Objective-C people use different frameworks to write BDD-style tests for their code. Some of them are: CedarKiwiSpecta With the inroduction of Swift we&#8217;ve decided to create BDD-style testing framework in pure Swift.After a couple weeks of implementation we are making the first public version of the framework called Sleipnir.Sleipnir is highly inspired by&#8230;<\/p>\n","protected":false},"author":58,"featured_media":9466,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[3],"tags":[],"coauthors":["Artur Termenji"],"class_list":["post-7033","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"aioseo_notices":[],"categories_data":[{"name":"Engineering","link":"https:\/\/railsware.com\/blog?category=development"}],"post_thumbnails":"https:\/\/railsware.com\/blog\/wp-content\/themes\/railsware\/vendors\/images\/article-thumbnail-default.jpg","amp_enabled":true,"_links":{"self":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/7033","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/users\/58"}],"replies":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/comments?post=7033"}],"version-history":[{"count":27,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/7033\/revisions"}],"predecessor-version":[{"id":14090,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/7033\/revisions\/14090"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media\/9466"}],"wp:attachment":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media?parent=7033"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/categories?post=7033"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/tags?post=7033"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/coauthors?post=7033"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}