{"action":"created","comment":{"url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/comments/62984319","id":62984319,"diff_hunk":"@@ -0,0 +1,422 @@\n+---\n+layout: post\n+title:  \"Using Kotlin to test Android applications\"\n+date:   2016-05-11 00:00:00\n+categories: Greenhouse update\n+tags: [android, testing, kotlin, java]\n+author: priit\n+share: true\n+published: true\n+comments: true\n+---\n+\n+Testing is an integral part of any kind of software development.\n+To ensure that code is ready to be deployed to production it is rarely\n+enough to just manually verify that new features are working.\n+When talking about code quality and reliability, importance of automated\n+testing cannot be overstated, be it regression tests, unit tests or UI tests.\n+\n+In this blog post we discuss yet another possibility of how to write automated\n+tests for your Android applications. Namely, we observe how [**Kotlin**](https://kotlinlang.org/),\n+one of the latest stable and noteworthy additions to the vast world of JVM based\n+languages, can be utilized for test automation of Android applications.\n+\n+<!--more-->\n+\n+## Few words about Kotlin\n+\n+Kotlin is an [open source language](https://github.com/JetBrains/kotlin)\n+developed and maintained by JetBrains, the company behind IntelliJ IDEA and\n+possibly your favorite IDE for Android development, Android Studio.\n+\n+After being in development for more than 5 years (first commit dating back to 2010-11-08),\n+the first stable version of the language [was released](http://blog.jetbrains.com/kotlin/2016/02/kotlin-1-0-released-pragmatic-language-for-jvm-and-android/)\n+earlier this year in February.\n+\n+Among its key characteristics we can list out\n+\n+- Support for both OO and functional style.\n+- Interoperability with Java code, meaning you can easily mix Kotlin into your existing Java codebase or *vice versa*.\n+- Superb tooling with plugins available for IntelliJ based IDEs, Eclipse, and text editors like Atom, Emacs, Sublime Text and Vim.\n+- Strong emphasis on clarity and type safety.\n+\n+## Android application testing in general\n+\n+Since the early days of Android ecosystem its tooling, library and framework support\n+for testing has significantly improved. From plain instrumentation tests and regular\n+JUnit tests we have come to all the way to convenient tools like\n+[AssertJ](http://square.github.io/assertj-android/),\n+[Espresso](https://google.github.io/android-testing-support-library/docs/espresso/index.html),\n+[Robolectric](http://robolectric.org/),\n+[Robotium](https://github.com/RobotiumTech/robotium),\n+[UI Automator](https://google.github.io/android-testing-support-library/docs/uiautomator/index.html)\n+and the list goes on. All of which make our jobs easier and consequently our lives happier.\n+\n+But it is rarely the case that your tests consist only of assertion statements. It is common\n+to have tons of utility and glue code just to prepare the test case, especially when you\n+are testing our more complicated user flows. And to make the matters worse, in some cases the\n+complexity of tests could easily exceed that of the main application. Those are exactly the\n+cases where the verbosity of Java could get in the way. Every now and then it is just bloody\n+convenient to open a file and loop over it's contents with one or two lines instead of\n+couple of dozen LOCs that are required to do so in Java.\n+\n+In those situations a language like Kotlin can really help you to step up your game. The\n+expressiveness and clean syntax, combined with nifty language idioms make it a huge\n+improvement over the clumsiness that Java comes with. But enough of the small talk,\n+let us see how it all works out in practice with a hands on example.\n+\n+## Plug Kotlin into your Android project\n+\n+> *For easier reference you can already grab complete source code of the example\n+application from [this GitHub repository](https://github.com/GreenhouseCI/kotlin-tests).\n+All code snippets to come will be extracted from there.*\n+\n+To start using Kotlin in your existing Android project you just have to add a few lines to your\n+project's Gradle build file, declaring which version of Kotlin you'd like to use and\n+which dependencies need to be additionally compiled. As far my experience goes,\n+[the official documentation](https://kotlinlang.org/docs/reference/using-gradle.html),\n+which seem to be great, will point you in the right direction. In any case, my `build.gradle`\n+build script ended up being like this:\n+\n+```groovy\n+apply plugin: 'com.android.application'\n+apply plugin: 'kotlin-android'\n+\n+buildscript {\n+    ext.kotlin_version = '1.0.+'\n+    repositories {\n+        jcenter()\n+        mavenCentral()\n+    }\n+    dependencies {\n+        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n+    }\n+}\n+\n+android {\n+    ...\n+}\n+\n+dependencies {\n+    compile \"org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version\"\n+    testCompile \"org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version\"\n+    testCompile \"org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version\"\n+    ...\n+}\n+```\n+\n+Let us tear down the extra lines. On the second line, right after Android plugin is applied,\n+we inject Kotlin Gradle plugin that targets Android build model by\n+`apply plugin: 'kotlin-android'`.\n+To make that plugin available we need to declare it as a `buildscript` level dependency with\n+\n+```groovy\n+dependencies {\n+    classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n+}\n+```\n+where the preceding `ext.kotlin_version = '1.0.+'` just selects the newest version of Kotlin from\n+1.0 series.\n+\n+Finally, under `dependencies` section we need to state that Kotlin _stdlib_ has to be compiled.\n+And as we are going to use Kotlin especially for testing purposes, it needs to be compiled for\n+tests as well.\n+\n+__One note about tests and their location in the source tree.__\n+\n+As unit and UI tests are usually stored in `src/test/java` and `src/androidTest/java`\n+respectively, we might want to maintain sanity in our codebase and keep Kotlin tests\n+in dedicated directories like `src/test/kotlin` and `src/androidTest/kotlin`. As these\n+are directories are not detected as Android test locations by default (*yet?*), we\n+need to make Gradle and Android Studio aware of them. This can be done in the `sourceSets`\n+section with\n+\n+```groovy\n+android {\n+    ...\n+    sourceSets {\n+        test.java.srcDirs += 'src/test/kotlin'\n+        androidTest.java.srcDirs += 'src/androidTest/kotlin'\n+    }\n+}\n+```\n+\n+## Our example app\n+\n+The [application](https://github.com/GreenhouseCI/kotlin-tests) created for\n+the purposes of this blog post is of course yet another stunning shopping list app. As expected,\n+it is possible to add some items to the list and remove them from there as you wish. To add\n+some spice into the mix and make things a little bit more __testable__, every item that is\n+inserted to the list passes through a formatter that removes all excess whitespace from the\n+input string. Critical business logic in the form of our formatter that we use to clear up\n+the strings lives in\n+[`Formatter.java`](https://github.com/GreenhouseCI/kotlin-tests/blob/master/app/src/main/java/com/greenhouseci/kotlin_tests/kotlintests/Formatter.java)\n+and looks like\n+\n+```java\n+public class Formatter {\n+\n+    public String stripMiddle(String s) {\n+        return s.replaceAll(\"\\\\s+\", \" \");\n+    }\n+\n+    public String stripLeft(String s) {\n+        return s.replaceAll(\"^\\\\s+\", \"\");\n+    }\n+\n+    public String stripRight(String s) {\n+        return s.replaceAll(\"\\\\s+$\", \"\");\n+    }\n+\n+    public String strip(String s) {\n+        return stripLeft(stripRight(stripMiddle(s)));\n+    }\n+}\n+```\n+\n+Bridge connecting user interactions with formatter resides conveniently in\n+[`MainActivityFragment`](https://github.com/GreenhouseCI/kotlin-tests/blob/master/app/src/main/java/com/greenhouseci/kotlin_tests/kotlintests/MainActivityFragment.java#L38).\n+\n+All in all, our app stands in it's full glory as depicted below:\n+<div style=\"text-align:center; padding: 20px 0;\"><img src=\"/assets/kotlin-app-screenshot.png\" alt=\"Screenshot\"/></div>\n+\n+Evidently we want to test out that whenever item description `EditText` is filled with anything\n+meaningful and \"Add item\" button is pressed, the newly added item is populated to the list.\n+Furthermore, it is of utmost importance to ensure that all the items that are added to the list\n+do not contain any superfluous whitespace characters. For that we need tests of course.\n+\n+## Unit tests\n+\n+Let us start off with unit tests for our `Formatter` class shown above. As already mentioned,\n+we'll store our unit tests under `src/test/kotlin`. Right there we have a test class\n+`FormatterTestKotlin` defined in\n+[`FormatterTestKotlin.kt`](https://github.com/GreenhouseCI/kotlin-tests/blob/master/app/src/test/kotlin/com/greenhouseci/kotlin_tests/kotlintests/FormatterTestKotlin.kt):\n+\n+```ruby\n+package com.greenhouseci.kotlin_tests.kotlintests\n+\n+import org.junit.Assert.assertEquals\n+import org.junit.Test\n+\n+\n+class FormatterTestKotlin {\n+\n+    private val formatter: Formatter = Formatter()\n+\n+    infix fun Any.equals(expected: Any) {\n+        assertEquals(expected, this)\n+    }\n+\n+    ...\n+\n+    @Test\n+    @Throws(Exception::class)\n+    fun testStrip() {\n+        val reference = \"my string\"\n+        val testStrings = arrayOf(\n+            reference,\n+            \" $reference \",\n+            \" $reference  \",\n+            \" \\t $reference \\t \",\n+            \" \\n  $reference \\n \",\n+            \"\\tmy \\n string\\t\"\n+        )\n+        testStrings.map { formatter.strip(it) equals reference }\n+    }\n+```\n+\n+A glimpse at the snippet above shows that while it is Kotlin code, there are some\n+familiar elements from regular Java already, like package name and import statements.\n+Further decomposing it reveals lack of semicolons in the end of the lines and differences\n+in variable and function declarations. But also some obvious similarities like code block\n+separation by usage of curly brackets and support for annotations.\n+\n+__Variable and function declaration__\n+\n+In our sample code we can see that declaring a value can be as simple as\n+\n+```groovy\n+val reference = \"my string\"\n+```\n+\n+where the type of the object that is assigned is implicitly guessed by compiler.\n+Or we can explicitly declare the type of the object by writing\n+\n+```groovy\n+val formatter: Formatter = Formatter()\n+```\n+\n+where the type information follows variable name with colon and precedes the value\n+to be assigned.\n+\n+Kotlin also provides an easy way to distinguish read-only and mutable fields.\n+It does so by exposing two types of keywords: `val` for declaring read-only fields\n+and `var` for variables that ought to be mutable. In the realm of Java, `val` corresponds\n+to the `final` modifier.\n+\n+> *It is also worth noting how we can assign Java object to a Kotlin variable without\n+any further ado.*\n+\n+Functions are defined using `fun` keyword followed by the function name and parameters\n+in parentheses. Return type and argument types are specified using the colon notation\n+just as it is with variable declaration. But as Kotlin has first class support for\n+user defined **infix** functions, as seen above, we can enrich the standard library\n+with syntactic sugar of our choosing with no effort whatsoever. For example here we\n+have replaced\n+\n+```java\n+assertEquals(expected, actual)\n+```\n+\n+checks with\n+\n+```java\n+actual equals expected\n+```\n+\n+statements for readability, utilizing our custom infix procedure `equals`.\n+\n+```java\n+infix fun Any.equals(expected: Any) {\n+    assertEquals(expected, this)\n+}\n+```\n+\n+Of course, all other gems of Kotlin are readily available to us, including\n+convenient array declaration, avoidance of clumsy `for` loops using `map` and\n+other functional elements.\n+\n+Last but not least, we annotate out test functions with `@Test` as usual to make\n+compiler aware of them.\n+\n+```java\n+@Test\n+fun myTestCase() { ... }\n+```\n+\n+## Espresso tests\n+\n+Making use of the interoperability of Java and Kotlin code we can blend Espresso into\n+our Kotlin test classes in the blink of an eye. That's how we can benefit both from\n+battle tested Espresso framework that makes testing UI components a bliss and\n+all the gems that Kotlin provides as a language to make writing our _glue_ code and other\n+testing logic as easy and clean as possible.\n+\n+As usual, we store our Espresso and other UI tests under `src/androidTest`, with a\n+little discrepancy that instead of `java` subdirectory, we store our Kotlin source code in\n+`kotlin` subdirectory. And in order to run Espresso tests we need to declare it as a dependency\n+in our build script plus set `AndroidJUnitRunner` as the default test instrumentation\n+runner, exactly like you would do with plain Espresso tests:\n+\n+```groovy\n+android {\n+    ...\n+    defaultConfig {\n+        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n+    }\n+}\n+\n+dependencies {\n+    ...\n+    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'\n+}\n+```\n+\n+Now to the test suite itself. Our example Espresso test class\n+[`EspressoKotlinTest`](https://github.com/GreenhouseCI/kotlin-tests/blob/master/app/src/androidTest/kotlin/com/greenhouseci/kotlin_tests/kotlintests/EspressoKotlinTest.kt)\n+looks like\n+\n+```ruby\n+@RunWith(AndroidJUnit4::class)\n+class EspressoKotlinTest {\n+\n+    ...\n+\n+    @Rule @JvmField\n+    var activityRule = ActivityTestRule<MainActivity>(MainActivity::class.java)\n+\n+    ...\n+\n+    @Test\n+    fun testAddItemsToList() {\n+        val formatter = Formatter()\n+        val lv = onData(anything()).inAdapterView(withId(R.id.lv_items))\n+\n+        var items = arrayOf(\n+            Pair(\"    item 1\", \"item 1\"),\n+            Pair(\"item 2  \", \"item 2\"),\n+            Pair(\"  item  \\n3  \", \"item 3\")\n+        )\n+\n+        items.map { pair -> addItemToList(pair.first) }\n+        items.mapIndexed { index, pair ->\n+            val expected = formatter.strip(pair.second)\n+            lv.atPosition(index).onChildView(withId(R.id.tv_list_item)).check(\n+                matches(withText(expected))\n+            )\n+        }\n+    }\n+}\n+```\n+\n+You can see that the Espresso test suite at hand doesn't differ much from the\n+unit tests we discussed in the previous section. And neither does it differ much from\n+your already existing Espresso tests written in Java. Truth is, that majority of the\n+code that does the heavy lifting is plain *Java* and other parts are almost\n+drop in replacements.\n+\n+As usual with instrumentation tests, we start off by creating an instrumented JUnit 4\n+test class by adding\n+\n+```ruby\n+@RunWith(AndroidJUnit4::class)\n+```\n+\n+annotation at the beginning of our test class definition. And naturally we can make\n+use of Espresso's built-in `ActivityTestRule` to handle our activity life cycle:\n+\n+```ruby\n+@Rule @JvmField\n+var activityRule = ActivityTestRule<MainActivity>(MainActivity::class.java)\n+```\n+\n+With one minor caveat that you need to annotate it with `@JvmField` to convert it\n+from Kotlin a *property*, that is unrecognized by JUnit, to a JVM field that is edible\n+by JUnit.\n+\n+Apart from these tiny details, you can just use the familiar Espresso API for the\n+test functions.\n+\n+## Running our Kotlin tests\n+\n+Since we have already updated our `sourceSets` in `build.gradle` we don't have to\n+go through any configuration magic any more. You can execute both unit and instrumentation\n+tests right from Android Studio by just right-clicking either on your tests packages or\n+Kotlin test files and clicking on \"Run 'Tests in ...'\" as can be seen below:\n+\n+<div style=\"text-align:center; padding: 20px 0;\"><img src=\"/assets/run-kotlin-tests-form-android-studio.png\" alt=\"Run Kotlin tests from Android Studio\"/></div>\n+\n+The very same minimal configuration logic applies also in case you should want to","path":"_posts/2016-05-11-android-testing-with-kotlin.md","position":399,"original_position":399,"commit_id":"a4272f875b62c1cc39eccc74989fe2a853d5c7fe","original_commit_id":"a4272f875b62c1cc39eccc74989fe2a853d5c7fe","user":{"login":"NikitaAvvakumov","id":5029403,"avatar_url":"https://avatars.githubusercontent.com/u/5029403?v=3","gravatar_id":"","url":"https://api.github.com/users/NikitaAvvakumov","html_url":"https://github.com/NikitaAvvakumov","followers_url":"https://api.github.com/users/NikitaAvvakumov/followers","following_url":"https://api.github.com/users/NikitaAvvakumov/following{/other_user}","gists_url":"https://api.github.com/users/NikitaAvvakumov/gists{/gist_id}","starred_url":"https://api.github.com/users/NikitaAvvakumov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NikitaAvvakumov/subscriptions","organizations_url":"https://api.github.com/users/NikitaAvvakumov/orgs","repos_url":"https://api.github.com/users/NikitaAvvakumov/repos","events_url":"https://api.github.com/users/NikitaAvvakumov/events{/privacy}","received_events_url":"https://api.github.com/users/NikitaAvvakumov/received_events","type":"User","site_admin":false},"body":"\"**also** applies ~~also in case you~~ should **you** want to execute your tests from **the** command line\"","created_at":"2016-05-12T08:46:20Z","updated_at":"2016-05-12T08:46:20Z","html_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com/pull/1#discussion_r62984319","pull_request_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/comments/62984319"},"html":{"href":"https://github.com/GreenhouseCI/blog.greenhouseci.com/pull/1#discussion_r62984319"},"pull_request":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/1","id":69628329,"html_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com/pull/1","diff_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com/pull/1.diff","patch_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com/pull/1.patch","issue_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues/1","number":1,"state":"open","locked":false,"title":"Android testing using Kotlin","user":{"login":"priitlatt","id":2756611,"avatar_url":"https://avatars.githubusercontent.com/u/2756611?v=3","gravatar_id":"","url":"https://api.github.com/users/priitlatt","html_url":"https://github.com/priitlatt","followers_url":"https://api.github.com/users/priitlatt/followers","following_url":"https://api.github.com/users/priitlatt/following{/other_user}","gists_url":"https://api.github.com/users/priitlatt/gists{/gist_id}","starred_url":"https://api.github.com/users/priitlatt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/priitlatt/subscriptions","organizations_url":"https://api.github.com/users/priitlatt/orgs","repos_url":"https://api.github.com/users/priitlatt/repos","events_url":"https://api.github.com/users/priitlatt/events{/privacy}","received_events_url":"https://api.github.com/users/priitlatt/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-05-11T07:48:30Z","updated_at":"2016-05-12T08:46:20Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b2d08c292f982335839cb060f5ce4f794a561871","assignee":{"login":"NikitaAvvakumov","id":5029403,"avatar_url":"https://avatars.githubusercontent.com/u/5029403?v=3","gravatar_id":"","url":"https://api.github.com/users/NikitaAvvakumov","html_url":"https://github.com/NikitaAvvakumov","followers_url":"https://api.github.com/users/NikitaAvvakumov/followers","following_url":"https://api.github.com/users/NikitaAvvakumov/following{/other_user}","gists_url":"https://api.github.com/users/NikitaAvvakumov/gists{/gist_id}","starred_url":"https://api.github.com/users/NikitaAvvakumov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NikitaAvvakumov/subscriptions","organizations_url":"https://api.github.com/users/NikitaAvvakumov/orgs","repos_url":"https://api.github.com/users/NikitaAvvakumov/repos","events_url":"https://api.github.com/users/NikitaAvvakumov/events{/privacy}","received_events_url":"https://api.github.com/users/NikitaAvvakumov/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/1/commits","review_comments_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/1/comments","review_comment_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/comments{/number}","comments_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues/1/comments","statuses_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/statuses/a4272f875b62c1cc39eccc74989fe2a853d5c7fe","head":{"label":"GreenhouseCI:kotlin","ref":"kotlin","sha":"a4272f875b62c1cc39eccc74989fe2a853d5c7fe","user":{"login":"GreenhouseCI","id":6448978,"avatar_url":"https://avatars.githubusercontent.com/u/6448978?v=3","gravatar_id":"","url":"https://api.github.com/users/GreenhouseCI","html_url":"https://github.com/GreenhouseCI","followers_url":"https://api.github.com/users/GreenhouseCI/followers","following_url":"https://api.github.com/users/GreenhouseCI/following{/other_user}","gists_url":"https://api.github.com/users/GreenhouseCI/gists{/gist_id}","starred_url":"https://api.github.com/users/GreenhouseCI/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GreenhouseCI/subscriptions","organizations_url":"https://api.github.com/users/GreenhouseCI/orgs","repos_url":"https://api.github.com/users/GreenhouseCI/repos","events_url":"https://api.github.com/users/GreenhouseCI/events{/privacy}","received_events_url":"https://api.github.com/users/GreenhouseCI/received_events","type":"Organization","site_admin":false},"repo":{"id":16066146,"name":"blog.greenhouseci.com","full_name":"GreenhouseCI/blog.greenhouseci.com","owner":{"login":"GreenhouseCI","id":6448978,"avatar_url":"https://avatars.githubusercontent.com/u/6448978?v=3","gravatar_id":"","url":"https://api.github.com/users/GreenhouseCI","html_url":"https://github.com/GreenhouseCI","followers_url":"https://api.github.com/users/GreenhouseCI/followers","following_url":"https://api.github.com/users/GreenhouseCI/following{/other_user}","gists_url":"https://api.github.com/users/GreenhouseCI/gists{/gist_id}","starred_url":"https://api.github.com/users/GreenhouseCI/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GreenhouseCI/subscriptions","organizations_url":"https://api.github.com/users/GreenhouseCI/orgs","repos_url":"https://api.github.com/users/GreenhouseCI/repos","events_url":"https://api.github.com/users/GreenhouseCI/events{/privacy}","received_events_url":"https://api.github.com/users/GreenhouseCI/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com","description":"The code powering http://blog.greenhouseci.com","fork":false,"url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com","forks_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/forks","keys_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/teams","hooks_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/hooks","issue_events_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues/events{/number}","events_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/events","assignees_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/assignees{/user}","branches_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/branches{/branch}","tags_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/tags","blobs_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/statuses/{sha}","languages_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/languages","stargazers_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/stargazers","contributors_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/contributors","subscribers_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/subscribers","subscription_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/subscription","commits_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues/comments{/number}","contents_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/contents/{+path}","compare_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/merges","archive_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/downloads","issues_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues{/number}","pulls_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls{/number}","milestones_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/milestones{/number}","notifications_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/labels{/name}","releases_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/releases{/id}","deployments_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/deployments","created_at":"2014-01-20T09:23:47Z","updated_at":"2014-06-11T09:52:42Z","pushed_at":"2016-05-11T08:57:08Z","git_url":"git://github.com/GreenhouseCI/blog.greenhouseci.com.git","ssh_url":"git@github.com:GreenhouseCI/blog.greenhouseci.com.git","clone_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com.git","svn_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com","homepage":null,"size":9886,"stargazers_count":0,"watchers_count":0,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"GreenhouseCI:gh-pages","ref":"gh-pages","sha":"a5b2ef7a9c6e6e436018be8681cbedfa2a9e140d","user":{"login":"GreenhouseCI","id":6448978,"avatar_url":"https://avatars.githubusercontent.com/u/6448978?v=3","gravatar_id":"","url":"https://api.github.com/users/GreenhouseCI","html_url":"https://github.com/GreenhouseCI","followers_url":"https://api.github.com/users/GreenhouseCI/followers","following_url":"https://api.github.com/users/GreenhouseCI/following{/other_user}","gists_url":"https://api.github.com/users/GreenhouseCI/gists{/gist_id}","starred_url":"https://api.github.com/users/GreenhouseCI/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GreenhouseCI/subscriptions","organizations_url":"https://api.github.com/users/GreenhouseCI/orgs","repos_url":"https://api.github.com/users/GreenhouseCI/repos","events_url":"https://api.github.com/users/GreenhouseCI/events{/privacy}","received_events_url":"https://api.github.com/users/GreenhouseCI/received_events","type":"Organization","site_admin":false},"repo":{"id":16066146,"name":"blog.greenhouseci.com","full_name":"GreenhouseCI/blog.greenhouseci.com","owner":{"login":"GreenhouseCI","id":6448978,"avatar_url":"https://avatars.githubusercontent.com/u/6448978?v=3","gravatar_id":"","url":"https://api.github.com/users/GreenhouseCI","html_url":"https://github.com/GreenhouseCI","followers_url":"https://api.github.com/users/GreenhouseCI/followers","following_url":"https://api.github.com/users/GreenhouseCI/following{/other_user}","gists_url":"https://api.github.com/users/GreenhouseCI/gists{/gist_id}","starred_url":"https://api.github.com/users/GreenhouseCI/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GreenhouseCI/subscriptions","organizations_url":"https://api.github.com/users/GreenhouseCI/orgs","repos_url":"https://api.github.com/users/GreenhouseCI/repos","events_url":"https://api.github.com/users/GreenhouseCI/events{/privacy}","received_events_url":"https://api.github.com/users/GreenhouseCI/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com","description":"The code powering http://blog.greenhouseci.com","fork":false,"url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com","forks_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/forks","keys_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/teams","hooks_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/hooks","issue_events_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues/events{/number}","events_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/events","assignees_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/assignees{/user}","branches_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/branches{/branch}","tags_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/tags","blobs_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/statuses/{sha}","languages_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/languages","stargazers_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/stargazers","contributors_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/contributors","subscribers_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/subscribers","subscription_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/subscription","commits_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues/comments{/number}","contents_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/contents/{+path}","compare_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/merges","archive_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/downloads","issues_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues{/number}","pulls_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls{/number}","milestones_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/milestones{/number}","notifications_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/labels{/name}","releases_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/releases{/id}","deployments_url":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/deployments","created_at":"2014-01-20T09:23:47Z","updated_at":"2014-06-11T09:52:42Z","pushed_at":"2016-05-11T08:57:08Z","git_url":"git://github.com/GreenhouseCI/blog.greenhouseci.com.git","ssh_url":"git@github.com:GreenhouseCI/blog.greenhouseci.com.git","clone_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com.git","svn_url":"https://github.com/GreenhouseCI/blog.greenhouseci.com","homepage":null,"size":9886,"stargazers_count":0,"watchers_count":0,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/1"},"html":{"href":"https://github.com/GreenhouseCI/blog.greenhouseci.com/pull/1"},"issue":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues/1"},"comments":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/GreenhouseCI/blog.greenhouseci.com/statuses/a4272f875b62c1cc39eccc74989fe2a853d5c7fe"}}}}