<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[AKJAW]]></title><description><![CDATA[A technical blog for Android, Kotlin Multiplatform and Testing by Aleksander Jaworski]]></description><link>https://akjaw.com/</link><image><url>https://akjaw.com/favicon.png</url><title>AKJAW</title><link>https://akjaw.com/</link></image><generator>Ghost 5.82</generator><lastBuildDate>Fri, 10 Apr 2026 10:41:55 GMT</lastBuildDate><atom:link href="https://akjaw.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Kotlin Multiplatform GitHub Actions Automated Pull Request Labels]]></title><description><![CDATA[Having automated labels makes the Code Review process much safer by preventing merges for broken Pull Requests, ensuring that the main branch is stable]]></description><link>https://akjaw.com/kotlin-multiplatform-gha-automated-labels/</link><guid isPermaLink="false">6559cd2d7c03e7200456b05f</guid><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Mon, 04 Mar 2024 16:41:32 GMT</pubDate><media:content url="https://akjaw.com/content/images/2024/03/kmp-github-actions-automatic-labeling.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2024/03/kmp-github-actions-automatic-labeling.png" alt="Kotlin Multiplatform GitHub Actions Automated Pull Request Labels"><p>This article is a continuation of my previous <a href="https://akjaw.com/kotlin-multiplatform-github-actions-ci-verification-labels/">Kotlin Multiplatform CI article</a> the code can be found in the <a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions?ref=akjaw.com">repository</a> and changes for this article are in this <a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/15/files?ref=akjaw.com">Pull Request</a>. </p>
<h2 id="reminder">Reminder</h2>
<p>The Code Review workflow runs the jobs based on the Pull Request labels, so the Android label only runs Android related Gradle tasks and the KMP label runs everything. </p>
<pre><code class="language-yaml">jobs:
  SetUp:
    runs-on: ubuntu-latest
    steps:
      - id: setVariables
        name: Set variables
        run: ...
    outputs:
      shouldRunKmp: ${{ steps.setVariables.outputs.shouldRunKmp }}
      shouldRunAndroid: ${{ steps.setVariables.outputs.shouldRunAndroid }}
      shouldRunIos: ${{ steps.setVariables.outputs.shouldRunIos }}
      shouldRunDesktop: ${{ steps.setVariables.outputs.shouldRunDesktop }}

  Build:
    needs: SetUp
    uses: ./.github/workflows/build.yml
    with:
      shouldRunKmp: ${{ needs.SetUp.outputs.shouldRunKmp }}
      shouldRunAndroid: ${{ needs.SetUp.outputs.shouldRunAndroid }}
      shouldRunIos: ${{ needs.SetUp.outputs.shouldRunIos }}
      shouldRunDesktop: ${{ needs.SetUp.outputs.shouldRunDesktop }}

  UnitTests:
    needs: SetUp
    uses: ./.github/workflows/test.yml
    with:
      shouldRunKmp: ${{ needs.SetUp.outputs.shouldRunKmp }}
      shouldRunAndroid: ${{ needs.SetUp.outputs.shouldRunAndroid }}
      shouldRunIos: ${{ needs.SetUp.outputs.shouldRunIos }}
      shouldRunDesktop: ${{ needs.SetUp.outputs.shouldRunDesktop }}

  AllowMerge:
    ...</code></pre>
<p>Thanks to this, the CI only builds things which are needed and allows for skipping unrelated jobs. There&apos;s no point in running the iOS build if there are only Android changes.</p>
<p>AllowMerge, will pass if the dependent jobs passed or were skipped, in case of failure it will fail, thus blocking merging. Additionally, when the PR is opened without any label, then it will also fail.</p>
<pre><code class="language-yaml">AllowMerge:
  if: always()
    runs-on: ubuntu-latest
    needs: [ Build, UnitTests ]
    steps:
      - run: |
          if [ ${{ github.event_name }} == pull_request ] &amp;&amp; [ ${{ join(github.event.pull_request.labels.*.name) == &apos;&apos; }} == true ]; then
            exit 1
          elif [ ${{ (contains(needs.Build.result, &apos;failure&apos;)) }} == true ] || [ ${{ (contains(needs.UnitTests.result, &apos;failure&apos;)) }} == true ]; then
            exit 1</code></pre>
<p>The above logic will prevent a broken main branch, however, the Pull Request author needs to be diligent and apply the correct labels, which might be problematic if the changes start including other platforms and the author forgets to update them.</p>
<div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text"><p><span>The workflow above only executes builds and tests based on what labels were applied. Thanks to this, the CI uses less resources when checking Pull Requests.</span></p></div></div>
<h2 id="automatic-labeling">Automatic Labeling</h2>
<p>The action for labeling is <a href="https://github.com/actions/labeler/tree/v5.0.0?ref=akjaw.com">Labeler v5</a>, which is configured as follows:</p>
<figure class="kg-card kg-code-card"><pre><code class="language-yaml">KMP:
- changed-files:
    - any-glob-to-any-file:
      - shared/**
      - konsist/**

Android:
- changed-files:
    - any-glob-to-any-file:
      - androidApp/**

iOS:
- changed-files:
    - any-glob-to-any-file:
      - iosApp/**

Desktop:
- changed-files:
    - any-glob-to-any-file:
      - desktopApp/**</code></pre><figcaption><p><span>The labels are set depending on what files were changed in this PR.</span></p></figcaption></figure>
<p>The repository for this project is a fork, and because of this <a href="https://github.com/actions/labeler?tab=readme-ov-file&amp;ref=akjaw.com#permissions">Labeler requires additional permissions</a>, to work correctly:</p>
<pre><code class="language-yaml">jobs:
  SetUp:
    permissions:
      contents: read
      pull-requests: write
    ...
</code></pre>
<p>The set-up outputs, which control which platform is run, are now based on the labeler output:</p>
<pre><code class="language-yaml">SetUp:
  ...
  steps:
    - id: labeler
      if: ${{ !contains(github.event.pull_request.labels.*.name, &apos;Ignore&apos;) }}
      uses: actions/labeler@v5
    - id: setVariables
      name: Set variables
      run: |
        ...
        hasKmpLabel=${{ contains(steps.labeler.outputs.all-labels, &apos;KMP&apos;) }}
        ...
  outputs:
    shouldRunKmp: ${{ steps.setVariables.outputs.shouldRunKmp }}
    shouldRunAndroid: ${{ steps.setVariables.outputs.shouldRunAndroid }}
    shouldRunIos: ${{ steps.setVariables.outputs.shouldRunIos }}
    shouldRunDesktop: ${{ steps.setVariables.outputs.shouldRunDesktop }}
    all-labels: ${{ steps.labeler.outputs.all-labels }}</code></pre>
<p>Additionally, if the PR already has a label called &apos;Ignore&apos; then the labeler does not apply any labels, allowing the PR to be merged without any checks which might be useful for Tooling, Documentation, Codeowner etc. changes. (More on this in the next section)</p>
<p>The AllowMerge job has one additional dependency to SetUp to access the labeler output:</p>
<pre><code class="language-yaml">AllowMerge:
  if: always()
  runs-on: ubuntu-latest
  needs: [ SetUp, Build, UnitTests ]
  steps:
    - run: |
        if [ ${{ github.event_name }} == pull_request ] &amp;&amp; [ ${{ join(github.event.pull_request.labels.*.name) == &apos;&apos; }} == true ] &amp;&amp; [ ${{ join(needs.SetUp.outputs.all-labels) == &apos;&apos; }} == true ]; then
          exit 1
        elif [ ${{ (contains(needs.Build.result, &apos;failure&apos;)) }} == true ] || [ ${{ (contains(needs.UnitTests.result, &apos;failure&apos;)) }} == true ]; then
          exit 1
        else
          exit 0
        fi
</code></pre>
<p>This output is used in the empty label condition to ensure that labels which were added automatically are also checked, preventing false positives.</p>
<div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text"><p><span>Thanks to this automation, the Pull Request author doesn&apos;t need to worry about applying the correct labels. All that needs to be done is to create the Pull Request either a draft or open.</span></p></div></div>
<h3 id="how-it-works">How it works</h3>
<p>When the PR is created, then the labeler adds the corresponding labels, which then dictate what jobs are run:</p>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2024/02/Screenshot-2024-02-12-at-18.24.03.png" class="kg-image" alt="Kotlin Multiplatform GitHub Actions Automated Pull Request Labels" loading="lazy" width="952" height="92" srcset="https://akjaw.com/content/images/size/w600/2024/02/Screenshot-2024-02-12-at-18.24.03.png 600w, https://akjaw.com/content/images/2024/02/Screenshot-2024-02-12-at-18.24.03.png 952w" sizes="(min-width: 720px) 720px"></figure>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2024/02/Screenshot-2024-02-12-at-18.50.29.png" class="kg-image" alt="Kotlin Multiplatform GitHub Actions Automated Pull Request Labels" loading="lazy" width="1316" height="624" srcset="https://akjaw.com/content/images/size/w600/2024/02/Screenshot-2024-02-12-at-18.50.29.png 600w, https://akjaw.com/content/images/size/w1000/2024/02/Screenshot-2024-02-12-at-18.50.29.png 1000w, https://akjaw.com/content/images/2024/02/Screenshot-2024-02-12-at-18.50.29.png 1316w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/17?ref=akjaw.com"><span>https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/17</span></a></figcaption></figure>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2024/02/Screenshot-2024-02-12-at-18.24.52.png" class="kg-image" alt="Kotlin Multiplatform GitHub Actions Automated Pull Request Labels" loading="lazy" width="924" height="74" srcset="https://akjaw.com/content/images/size/w600/2024/02/Screenshot-2024-02-12-at-18.24.52.png 600w, https://akjaw.com/content/images/2024/02/Screenshot-2024-02-12-at-18.24.52.png 924w" sizes="(min-width: 720px) 720px"></figure>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2024/02/Screenshot-2024-02-12-at-18.38.52.png" class="kg-image" alt="Kotlin Multiplatform GitHub Actions Automated Pull Request Labels" loading="lazy" width="1326" height="632" srcset="https://akjaw.com/content/images/size/w600/2024/02/Screenshot-2024-02-12-at-18.38.52.png 600w, https://akjaw.com/content/images/size/w1000/2024/02/Screenshot-2024-02-12-at-18.38.52.png 1000w, https://akjaw.com/content/images/2024/02/Screenshot-2024-02-12-at-18.38.52.png 1326w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/19?ref=akjaw.com"><span>https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/19</span></a></figcaption></figure>
<h3 id="ignoring-the-labeling">Ignoring the labeling</h3>
<p>There are some cases where there are no code changes, like tooling or documentation updates. In such cases, opening the PR with no matching directory changes will fail, because there are no labels.</p>
<p>However, the workflow logic has a special case for the <strong>Ignore</strong> label, which prevents labeler from being run (and accidentally adding labels when it shouldn&apos;t):</p>
<pre><code class="language-yaml">SetUp:
  ...
  steps:
    - id: labeler
      if: ${{ !contains(github.event.pull_request.labels.*.name, &apos;Ignore&apos;) }}
      uses: actions/labeler@v5
  ...</code></pre>
<p><strong>AllowMerge</strong> will pass because there is the <strong>Ignore </strong>label, allowing the PR to merge.</p>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2024/03/Screenshot-2024-03-04-at-16.49.51-1.png" class="kg-image" alt="Kotlin Multiplatform GitHub Actions Automated Pull Request Labels" loading="lazy" width="646" height="37" srcset="https://akjaw.com/content/images/size/w600/2024/03/Screenshot-2024-03-04-at-16.49.51-1.png 600w, https://akjaw.com/content/images/2024/03/Screenshot-2024-03-04-at-16.49.51-1.png 646w"></figure>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2024/02/Screenshot-2024-02-24-at-11.12.51.png" class="kg-image" alt="Kotlin Multiplatform GitHub Actions Automated Pull Request Labels" loading="lazy" width="951" height="453" srcset="https://akjaw.com/content/images/size/w600/2024/02/Screenshot-2024-02-24-at-11.12.51.png 600w, https://akjaw.com/content/images/2024/02/Screenshot-2024-02-24-at-11.12.51.png 951w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/21?ref=akjaw.com"><span>https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/21</span></a></figcaption></figure>
<h2 id="summary">Summary</h2>
<p>Having automated labels makes the Code Review process much safer by preventing merges for broken Pull Requests, ensuring that the main branch is stable (The only exception being adding the Ignore label by accident). </p>
<p>Additionally, the PR authors don&apos;t need to remember which labels to add or even add them at all, labeler takes care of this when creating a Pull Request.</p>
<h2 id></h2>]]></content:encoded></item><item><title><![CDATA[How I use Mutation Testing to Drive Good Test Case Coverage]]></title><description><![CDATA[Automated testing is important for efficient software development, catching bugs within minutes and saving valuable developer time. Mutation Testing validates test suites by altering production code logic. When tests pass despite code mutations, it signals missing test cases. ]]></description><link>https://akjaw.com/using-mutation-testing-for-good-test-coverage/</link><guid isPermaLink="false">653b901f7c03e7200456ae0e</guid><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Tue, 05 Dec 2023 15:05:18 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/11/mutation-testing-3.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2023/11/mutation-testing-3.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"><p>Testing is an important part of software development, a good automated test suite can catch bugs in a matter of minutes. Automated tests are usually run as part of a Continuous Integration, which runs on a separate machine without much involvement of the developer.</p>
<p>Comparing that with manual testing, which usually: builds the whole app, requires clicking through it, sometimes even changing network responses to test edge cases (e.g. errors, missing data). All those manual steps performed through the day add up pretty quickly (not to mention the whole lifetime of the project) costing developers a lot of time which could be spent elsewhere. </p>
<p>However, to have a good automated test suite, it needs to cover all edge cases for the important parts of the code. Important code could be just complex logic which everyone is scared to touch in fear of breaking it. It might also be code which is often changed, and with a little bit of oversight might introduce regressions (i.e. bugs).</p>
<p>Unfortunately, having a good test suite isn&apos;t easy, every developer must be diligent and add tests for every logic condition. When the <em>Author</em> forgets to do so, then a <em>Code Reviewer</em> should catch this and raise the missing test cases.</p>
<p>The good news is, that <strong>Mutation Testing</strong> is an approach which allows for quickly catching missing test cases.</p>
<div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text"><p><span>Automated testing is important for efficient software development, catching bugs within minutes and saving valuable developer time. To ensure a robust test suite, diligence is required from every developer.</span></p></div></div>
<h2 id="whats-mutation-testing">What&apos;s mutation testing</h2>
<p>In simple terms, Mutation Testing involves changing the production code and then running the tests. If there&apos;s a failure after this change, then usually this condition is covered by tests*. However, if all tests pass, then most likely there is a missing test case for this condition.</p>
<p>*  Sometimes even if some test fails after the &quot;mutation&quot; it&apos;s good to double-check if the test fails for the correct reasons. For example, the test focuses on some other part of the production code, but fails as a side effect of this mutation.</p>
<h3 id="boundary-values-example">Boundary values example	</h3>
<p>Take a look at this example production code:</p>
<pre><code class="language-kotlin">enum class Priority {
    LOW,
    HIGH,
}

fun convertToPriority(value: Int): Priority? {
    if (value !in 0..100) return null

    return if (value &lt;= 100 &amp;&amp; value &gt; 50) {
        Priority.HIGH
    } else {
        Priority.LOW
    }
}</code></pre>
<p>The logic could be simplified, but I wanted to keep it this way to simulate logic with a lot of conditions which might be tricky to test. There are some bugs hidden in the above code, which the current test suite does not detect:</p>
<pre><code class="language-kotlin">@Test
fun `When value is -1 then null is returned`() {
    convertToPriority(-1) shouldBe null
}

@Test
fun `When value is 101 then null is returned`() {
    convertToPriority(101) shouldBe null
}

@Test
fun `When value is 51 then High priority is returned`() {
    convertToPriority(51) shouldBe Priority.HIGH
}

@Test
fun `When value is 50 then Low priority is returned`() {
    convertToPriority(50) shouldBe Priority.LOW
}</code></pre>
<p>Looking at the test cases above, it might seem that everything is covered, however when it comes to <a href="https://akjaw.com/members/testing-mistakes-part-3/">boundary values it&apos;s really important to test all of them</a>. In the function above, the boundary values are 100, 50 and 0.</p>
<p>To catch the missing test cases, it&apos;s as easy as changing the upper boundary value to something smaller, like 99:</p>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/11/Screenshot-2023-11-18-at-08.59.48.png" class="kg-image" alt="How I use Mutation Testing to Drive Good Test Case Coverage" loading="lazy" width="873" height="52" srcset="https://akjaw.com/content/images/size/w600/2023/11/Screenshot-2023-11-18-at-08.59.48.png 600w, https://akjaw.com/content/images/2023/11/Screenshot-2023-11-18-at-08.59.48.png 873w" sizes="(min-width: 720px) 720px"></figure>
<p>The production code treats 100 as a High priority, so that&apos;s probably the requirement. However, after this change, all tests still pass, meaning that there&apos;s a missing test case for checking it:</p>
<pre><code class="language-kotlin">@Test
fun `When value is 100 then High priority is returned`() {
    convertToPriority(100) shouldBe Priority.HIGH
}</code></pre>
<p>Another missing test case is for the lower boundary in the first condition:</p>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/11/Screenshot-2023-11-18-at-09.03.25.png" class="kg-image" alt="How I use Mutation Testing to Drive Good Test Case Coverage" loading="lazy" width="875" height="49" srcset="https://akjaw.com/content/images/size/w600/2023/11/Screenshot-2023-11-18-at-09.03.25.png 600w, https://akjaw.com/content/images/2023/11/Screenshot-2023-11-18-at-09.03.25.png 875w" sizes="(min-width: 720px) 720px"></figure>
<p>All tests still pass, so the following test needs to be added:</p>
<pre><code class="language-Kotlin">@Test
fun `When value is 0 then Low priority is returned`() {
    convertToPriority(0) shouldBe Priority.LOW
}</code></pre>
<h3 id="mutation-testing-cheat-sheet">Mutation testing Cheat Sheet</h3>
<p>Every condition like <em>ifs</em>, <em>filters</em> or anything which uses a <em>boolean</em> can be easily mutated by:</p>
<ul><li>Commenting it out altogether (removing a condition branch)</li><li>Changing boundary values in comparisons (like in the example above)</li><li>Negating the condition (e.g. using <em>!</em>, <em>.not()</em>, or switching from <em>any</em> to <em>all</em>)</li></ul>
<p>Other code mutations could be returning a hard-coded value intentionally (e.g. an empty list, error or null)</p>
<div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text"><p><span>Mutation Testing validates test suites by altering production code logic. When tests pass despite code mutations, it signals missing test cases. Special attention should be paid to boundary values and conditions.</span></p></div></div>
<h2 id="why-code-coverage-doesnt-tell-the-whole-truth">Why Code Coverage doesn&apos;t tell the whole truth</h2>
<p>Code coverage is a misleading metric, look at this example:</p>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/11/Screenshot-2023-11-18-at-09.26.49.png" class="kg-image" alt="How I use Mutation Testing to Drive Good Test Case Coverage" loading="lazy" width="897" height="359" srcset="https://akjaw.com/content/images/size/w600/2023/11/Screenshot-2023-11-18-at-09.26.49.png 600w, https://akjaw.com/content/images/2023/11/Screenshot-2023-11-18-at-09.26.49.png 897w" sizes="(min-width: 720px) 720px"></figure>
<p>Looking at the above result it seems that everything is covered, however as previously pointed out, the above code has issues with the boundary values. So even though every line is covered, the test suite is not complete and is missing some cases. </p>
<p>Coverage could be used for just checking untested lines, however, for complex conditions it can be misleading and give false confidence that every case is covered.</p>
<div class="kg-card kg-callout-card kg-callout-card-red"><div class="kg-callout-text"><p><span>Code Coverage cannot be used as a metric for how good a test suite actually is. It gives misleading confidence, which makes it easier to introduce bugs into productions.</span></p></div></div>
<h2 id="writing-code">Writing Code</h2>
<p>When I&apos;m writing code, most of the time I&apos;m using Test Driven Development, which in practice should mean that there is no production code that doesn&apos;t have a corresponding test.</p>
<p>There are cases where I don&apos;t use TDD, or that the logic is so complex that I wasn&apos;t able to figure out all test cases for it. When that happens, Mutation Testing comes in handy, because it quickly allows me to find missing cases.</p>
<h2 id="reviewing-code">Reviewing Code</h2>
<p>When reviewing code which has tests, I usually open both the production code and the tests side by side in a split view. I first read through the test cases and try to understand the requirements of the class.</p>
<p>After I&apos;m done checking the existing code, I try to find any missing cases through mutation testing. Thanks to this, I don&apos;t need to spend a lot of time scanning through all test cases and checking the corresponding production code.</p>
<p>Besides finding missing test cases, mutation testing can also find tests which are passing for the wrong reason. For example, the production code uses condition A and B. The test verifies that when condition A is met, it passes. However, through mutation testing, condition A is removed but the test still passes. This means that the test passes for the wrong reason and needs to be changed.</p>
<h2 id="summary">Summary</h2>
<p>To ensure that something is properly tested you first have to fully understand the code, mutation testing is kind of shortcut for this. The rule of thumb is that, if the changed code doesn&apos;t cause the test to fail, then some test case is missing.</p>
<p>This technique is helpful for driving good test coverage, it doesn&apos;t need to be used every time. There are less important classes which don&apos;t need to have perfect test coverage.</p>
<p>The main focus for using mutation testing should be on code which:</p>
<ul><li>Is changed a lot (because changes can introduce regressions, and automated tests help with catching them)</li><li>Is critical for the functionality of the software (having tests for stuff which is only used for development might be overkill)</li><li>Has really complex logic, making it hard to change without braking anything. In such situations, tests can act like a guiding lighthouse, if everything is green, then proceed. </li></ul>
<p>If you&apos;re interested in learning more about testing in-general, checkout my article series on this topic:</p>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/5-beginner-testing-mistakes-i-noticed-while-working-with-less-experienced-developers/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">5 Beginner Testing Mistakes I Noticed While Working with Less Experienced Developers</div><div class="kg-bookmark-description">Having a good test suite helps with catching regressions / bugs a lot faster, and gives developers more confidence when merging / releasing their product. In this series, I&#x2019;ll share my recommendations for improving your tests.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"><span class="kg-bookmark-author">Aleksander Jaworski</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/04/hero-part1-3.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"></div></a></figure>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/members/testing-mistakes-part-2/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">21 Testing Mistakes - Part 2</div><div class="kg-bookmark-description">The second part of my Testing Mistakes series which is only available to Members / Subscribers</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"><span class="kg-bookmark-author">Aleksander Jaworski</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/04/hero-part2-1.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"></div></a></figure>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/members/testing-mistakes-part-3/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">21 Testing Mistakes - Part 3</div><div class="kg-bookmark-description">The third part of my Testing Mistakes series which is only available to Members / Subscribers</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"><span class="kg-bookmark-author">Aleksander Jaworski</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/04/hero-part3.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"></div></a></figure>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/members/testing-mistakes-part-4/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">21 Testing Mistakes - Part 4</div><div class="kg-bookmark-description">The fourth part of my Testing Mistakes series which is only available to Members / Subscribers</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"><span class="kg-bookmark-author">Aleksander Jaworski</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/04/hero-part4-1.png" alt="How I use Mutation Testing to Drive Good Test Case Coverage"></div></a></figure>
<p></p>]]></content:encoded></item><item><title><![CDATA[Adding Konsist and Ktlint to a GitHub Actions Continuous Integration]]></title><description><![CDATA[Automating code and architecture checks in the Continuous Integration pipeline can significantly reduce Code Review time and prevent human forgetfulness.]]></description><link>https://akjaw.com/konsist-and-ktlint-in-github-actions-continous-integration/</link><guid isPermaLink="false">6536a5eb7c03e7200456ad92</guid><category><![CDATA[CI/CD]]></category><category><![CDATA[GitHub Actions]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Wed, 01 Nov 2023 07:29:45 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/10/github-actions-konsist-ktlint-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2023/10/github-actions-konsist-ktlint-1.png" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration"><p>This article will focus on adding linter and code checks to a GitHub Actions Code Review workflow. The examples here are based on my Kotlin Multiplatform GitHub Actions repository, but all workflows and jobs are applicable to any Kotlin / Android project.</p>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/AKJAW/kotlin-multiplatform-github-actions?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - AKJAW/kotlin-multiplatform-github-actions: Repository showcasing GitHub Actions for Kotlin Multiplatform</div><div class="kg-bookmark-description">Repository showcasing GitHub Actions for Kotlin Multiplatform - GitHub - AKJAW/kotlin-multiplatform-github-actions: Repository showcasing GitHub Actions for Kotlin Multiplatform</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/d8f5b69118bad60264df761db889bbaf60f88fe0ef118eed1b97c81ccc08dbfe/AKJAW/kotlin-multiplatform-github-actions" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration"></div></a></figure>
<p>However, before diving into the implementation, I&apos;d like to provide some arguments why it&apos;s a good idea to do this.</p>
<h2 id="why-are-such-automations-needed">Why are such automations needed?</h2>
<p>Enforcing formatting and guidelines usually happens at the Code Review stage. You open a Pull Request, and a teammate informs you that a something is formatted incorrectly, or that the codebase uses a different convention for naming or packaging.</p>
<p>Verifying this manually takes more time, as the person doing the Code Review needs to pay extra attention to these things. While additionally trying to understand the changes in the PR. This leads to increased Code Review time and then additional time for fixing all the issues pointed out.</p>
<p>Besides the increased feedback loop, there is still a reliance on the human memory, the Reviewer might just forget about the conventions or be in a hurry, thus allowing the introduction of these &quot;inconsistencies&quot; into the codebase.</p>
<p>Automating such steps in the Continuous Integration pipeline should result in a healthier codebase, where every Pull Request follows the project guidelines. This eliminates the human forgetfulness aspect and also cuts down the feedback loop, because as soon as the PR is opened the verification is run and the author knows that some things need to be fixed.  </p>
<div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text"><p><span>Automating code and architecture checks in the Continuous Integration pipeline can significantly reduce Code Review time and prevent human forgetfulness, ensuring a healthier codebase that follows the project guidelines.</span></p></div></div>
<h2 id="konsist">Konsist</h2>
<p>I&apos;ve recently started playing around <a href="https://docs.konsist.lemonappdev.com/getting-started/readme?ref=akjaw.com">Konsist</a>, and I was blown away by its ease of use and power. You basically write tests which asserts something on your Kotlin files / classes / properties. The added benefit is that the tests are debuggable, meaning that it&apos;s easy to create the test inside the debugger evaluation window.</p>
<p>I won&apos;t go into the details of how to set up Konsist, because the official documentation is a much better resource for that. I can just say that I put Konsist into a separate module to decrease the build / configuration time.</p>
<p>As for calling Konsist inside GitHub Actions, it&apos;s as simple as running all tests inside the Konsist module:</p>
<pre><code class="language-yaml">name: Konsist

on:
  workflow_call:

jobs:
  Konsist:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-java@v3
        with:
          distribution: &quot;adopt&quot;
          java-version: &quot;11&quot;
  
      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - run: ./gradlew :konsist:testDebugUnitTest</code></pre>
<h2 id="ktlint">Ktlint</h2>
<p>While Konsist focuses on more Architectural / Structural guidelines, <a href="https://pinterest.github.io/ktlint/1.0.1/?ref=akjaw.com">Ktlint</a> focuses on code style conventions / standards. Both of them can work together to create a more predictable and healthy codebase.</p>
<p>In the example repository, Ktlint is added through a <a href="https://github.com/JLLeitschuh/ktlint-gradle?ref=akjaw.com">Gradle plugin</a>, meaning that it is installed alongside the project and can be run through a Gradle Task:</p>
<pre><code class="language-yaml">name: Ktlint

on:
  workflow_call:

jobs:
  Ktlint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-java@v3
        with:
          distribution: &quot;adopt&quot;
          java-version: &quot;11&quot;
  
      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - run: ./gradlew ktlintCheck</code></pre>
<p>If your project doesn&apos;t use a Gradle plugin, but the Ktlint CLI, then it can be used on the GitHub machine like this:</p>
<pre><code class="language-yaml">...
jobs:
  Ktlint:
    runs-on: ubuntu-latest

    steps:
      ...

      - name: Ktint set up
        run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.50.0/ktlint &amp;&amp; chmod a+x ktlint &amp;&amp; sudo mv ktlint /usr/local/bin/

      - run: ktlint &apos;!**/build/**&apos;</code></pre>
<p></p>
<div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text"><p><span>To save time on linting errors, a pre-commit hook could be installed which auto formats the code before committing.</span></p></div></div>
<h2 id="running-konsist-and-ktlint-before-other-verifications">Running Konsist and Ktlint before other verifications</h2>
<p>As you might&apos;ve noticed, both the above workflows are declared as <strong>workflow_call,</strong> allowing them to be called from other workflows. If the other verifications like UnitTests or Builds ale also <strong>workflow_call</strong>s<strong>,</strong> then they can be set up in the following way:</p>
<pre><code class="language-yaml">name: Code review

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != &apos;refs/heads/main&apos; }}

on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:
    types: [opened, ready_for_review, synchronize]
    branches:
      - main

jobs:
  Konsist:
    uses: ./.github/workflows/konsist.yml

  Ktlint:
    uses: ./.github/workflows/ktlint.yml

  Build:
    needs: [ Konsist, Ktlint ]
    uses: ./.github/workflows/build.yml

  UnitTests:
    needs: [ Konsist, Ktlint ]
    uses: ./.github/workflows/test.yml
</code></pre>
<p>Using <a href="https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow?ref=akjaw.com#defining-prerequisite-jobs"><strong>needs</strong></a> inside the <em>Build</em> and <em>UnitTests</em> jobs forces both of them to wait for <em>Konsist</em> and <em>Ktlint</em> to complete successfully before starting. So for example if <em>Konsist</em> passes, but <em>Ktlint</em> fails then <em>Build</em> and <em>UnitTests</em> are not started, thus saving precious billing minutes and gives faster feedback to the PR author.</p>
<div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-text"><p dir="ltr"><span>As pointed out by </span><a href="https://twitter.com/GakisStylianos?ref=akjaw.com" rel="noreferrer"><span>Stylianos Gakis</span></a><span> on Twitter, when billing minutes are not a problem it might be better to always run all verifications, even when Ktlint or Konsist fails. This gives the fastest feedback loop without having to re-run jobs.</span></p></div></div>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/10/Screenshot-2023-10-27-at-15.24.10.png" class="kg-image" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration" loading="lazy" width="1762" height="826" srcset="https://akjaw.com/content/images/size/w600/2023/10/Screenshot-2023-10-27-at-15.24.10.png 600w, https://akjaw.com/content/images/size/w1000/2023/10/Screenshot-2023-10-27-at-15.24.10.png 1000w, https://akjaw.com/content/images/size/w1600/2023/10/Screenshot-2023-10-27-at-15.24.10.png 1600w, https://akjaw.com/content/images/2023/10/Screenshot-2023-10-27-at-15.24.10.png 1762w" sizes="(min-width: 720px) 720px"><figcaption><span>A failed pre-step</span></figcaption></figure>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/10/Screenshot-2023-10-27-at-20.21.49.png" class="kg-image" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration" loading="lazy" width="1726" height="820" srcset="https://akjaw.com/content/images/size/w600/2023/10/Screenshot-2023-10-27-at-20.21.49.png 600w, https://akjaw.com/content/images/size/w1000/2023/10/Screenshot-2023-10-27-at-20.21.49.png 1000w, https://akjaw.com/content/images/size/w1600/2023/10/Screenshot-2023-10-27-at-20.21.49.png 1600w, https://akjaw.com/content/images/2023/10/Screenshot-2023-10-27-at-20.21.49.png 1726w" sizes="(min-width: 720px) 720px"><figcaption><span>Successful execution</span></figcaption></figure>
<h2 id="summary">Summary</h2>
<p>Having a good automation for these types of checks:</p>
<ul><li>Enforces good practices across the whole codebase by blocking incorrect Pull Requests</li><li>Makes reviewing code easier and faster, because less attention needs to be given to the naming / packaging / styling etc</li><li>Speeds up Pull Request merges, because the CI gives fast feedback about what should be fixed</li><li>They are run automatically, so there&apos;s no need to manually check them before opening a PR</li></ul>
<p>If you&apos;d like to learn more about good GitHub Actions practices for actions and workflows, feel free to check out my previous article:</p>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/github-actions-reducing-duplication-boilerplate/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub Actions Reducing Duplication / Boilerplate</div><div class="kg-bookmark-description">YAML files, which are not that easy to maintain, changes are usually copied and pasted across multiple files. This article shows that there&#x2019;s a better way.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration"><span class="kg-bookmark-author">Aleksander Jaworski</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/09/github-actions-reducing-duplication.png" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration"></div></a></figure>
<h2 id="kotlin-multiplatform-bonus">Kotlin Multiplatform Bonus</h2>
<p>Because Kotlin Multiplatform PRs can be iOS only, it makes no sense to run Ktlint or Konsist on them.  So if there are no Kotlin changes, then both of the jobs should just be skipped. However, GitHub Actions treats skipped jobs as failed thus blocks merging, this means that the approach needs to be different.</p>
<p>The changes which add the Kotlin Multiplatform Label logic (From my <a href="https://akjaw.com/kotlin-multiplatform-github-actions-ci-verification-labels/">previous article</a>) can be found inside <a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/14/files?ref=akjaw.com">this Pull Request</a>. The basic idea is that the labels are passed in from the Code Review workflow (I&apos;ll omit Ktlint because it&apos;s the same).</p>
<pre><code class="language-yaml">...

jobs:
  ...

  Konsist:
    needs: SetUp
    uses: ./.github/workflows/konsist.yml
    with:
      shouldRunKmp: ${{ needs.SetUp.outputs.shouldRunKmp }}
      shouldRunAndroid: ${{ needs.SetUp.outputs.shouldRunAndroid }}
      shouldRunDesktop: ${{ needs.SetUp.outputs.shouldRunDesktop }}

  ...</code></pre>
<p>Then used like this</p>
<figure class="kg-card kg-code-card"><pre><code class="language-yaml">name: Konsist

on:
  workflow_call:
    inputs:
      shouldRunKmp:
        required: true
        type: string
      shouldRunAndroid:
        required: true
        type: string
      shouldRunDesktop:
        required: true
        type: string

jobs:
  Konsist:
    runs-on: ubuntu-latest

    steps:
      ...

      - run: |
          if ${{ inputs.shouldRunKmp == &apos;true&apos; || inputs.shouldRunAndroid == &apos;true&apos; || inputs.shouldRunDesktop == &apos;true&apos; }}; then
            ./gradlew :konsist:testDebugUnitTest
          else 
            exit 0
          fi
</code></pre><figcaption><p><span>Bash conditional: either the Gradle task is run, or the job completes successfully.</span></p></figcaption></figure>
<p>Another approach could be using an if for the run step:</p>
<figure class="kg-card kg-code-card"><pre><code class="language-yaml">    steps:
      ...

      - if: ${{ inputs.shouldRunKmp == &apos;true&apos; || inputs.shouldRunAndroid == &apos;true&apos; || inputs.shouldRunDesktop == &apos;true&apos; }}
        run: ./gradlew :konsist:testDebugUnitTest</code></pre><figcaption><p><span>Step conditional: the step is skipped, but the job completes successfully.</span></p></figcaption></figure>
<p>So in cases when it doesn&apos;t make sense, Konsist and Ktlint are not executed:</p>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/10/Screenshot-2023-10-27-at-20.29.28.png" class="kg-image" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration" loading="lazy" width="1548" height="748" srcset="https://akjaw.com/content/images/size/w600/2023/10/Screenshot-2023-10-27-at-20.29.28.png 600w, https://akjaw.com/content/images/size/w1000/2023/10/Screenshot-2023-10-27-at-20.29.28.png 1000w, https://akjaw.com/content/images/2023/10/Screenshot-2023-10-27-at-20.29.28.png 1548w" sizes="(min-width: 720px) 720px"><figcaption><span>&quot;Skipped&quot; pre-step calls, note the execution time</span></figcaption></figure>
<p>However, for iOS there is <a href="https://github.com/realm/SwiftLint?ref=akjaw.com">SwiftLint</a> which could be used when the iOS label is applied to the PR, thus ensuring iOS style guidelines. Adding a SwiftLint job to the above workflow would be pretty much the same as for the Konsist job, just the command, parameters and condition would be different.</p>
<p>If you want to learn more about using GitHub Actions for a Kotlin Multiplatform repository, you can read my other article on this topic:</p>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/kotlin-multiplatform-github-actions-ci-verification-labels/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Kotlin Multiplatform GitHub Actions CI Verification using Labels</div><div class="kg-bookmark-description">Having automatic verifications is an important part of Software Development, as the project and team grows it becomes crucial that no regressions are introduced into the &#x201C;main&#x201D; branch by mistake.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration"><span class="kg-bookmark-author">Aleksander Jaworski</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/09/kmp-github-actions.png" alt="Adding Konsist and Ktlint to a GitHub Actions Continuous Integration"></div></a></figure>
<p></p>]]></content:encoded></item><item><title><![CDATA[GitHub Actions Reducing Duplication / Boilerplate]]></title><description><![CDATA[YAML files, which are not that easy to maintain, changes are usually copied and pasted across multiple files. This article shows that there's a better way.]]></description><link>https://akjaw.com/github-actions-reducing-duplication-boilerplate/</link><guid isPermaLink="false">64f0dfa87c03e7200456aa18</guid><category><![CDATA[GitHub Actions]]></category><category><![CDATA[CI/CD]]></category><category><![CDATA[Multiplatform]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Sat, 09 Sep 2023 07:34:34 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/09/github-actions-reducing-duplication.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2023/09/github-actions-reducing-duplication.png" alt="GitHub Actions Reducing Duplication / Boilerplate"><p>In my <a href="https://akjaw.com/kotlin-multiplatform-github-actions-ci-verification-labels/">previous article</a>, I&apos;ve shown how to set up a CI for a Kotlin Multiplatform repository. In this post, I&apos;ll focus on how to remove GitHub Actions boilerplate. </p>
<div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text"><p><span>If you don&apos;t use Kotlin Multiplatform, or even Kotlin, don&apos;t worry. The main focus of this article is platform-agnostic (YAML and bash). It touches on topics like extracting logic, composite actions, adding job variables.</span></p></div></div>
<p>The CI verification consists of building the project and running tests on: the main branch and PRs. However, for PRs depending on what label the PR has only selected targets will be run, which saves time and costs (for paid plans).</p>
<p>The repository (which is a Kotlin Multiplatform project for Android, Desktop and iOS) used in this article is available in this repository:</p>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/AKJAW/kotlin-multiplatform-github-actions?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - AKJAW/kotlin-multiplatform-github-actions: Repository showcasing GitHub Actions for Kotlin Multiplatform</div><div class="kg-bookmark-description">Repository showcasing GitHub Actions for Kotlin Multiplatform - GitHub - AKJAW/kotlin-multiplatform-github-actions: Repository showcasing GitHub Actions for Kotlin Multiplatform</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="GitHub Actions Reducing Duplication / Boilerplate"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/ac51c56168b74c053100bb6e48d7ce08df72b1a24c0ad229e6b5339c7f3abc3c/AKJAW/kotlin-multiplatform-github-actions" alt="GitHub Actions Reducing Duplication / Boilerplate"></div></a></figure>
<h2 id="the-workflows">The Workflows</h2>
<p>Without going into the implementation details of what is called where, here&apos;s a broad overview of the workflow structure:</p>
<figure class="kg-card kg-code-card"><pre><code class="language-yaml">name: Code review

on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:
    types: [opened, ready_for_review, synchronize]
    branches:
      - main

jobs:
  Build:
    uses: ./.github/workflows/build.yml

  UnitTests:
    uses: ./.github/workflows/test.yml

  AllowMerge:
    if: always()
    runs-on: ubuntu-latest
    needs: [ Build, UnitTests ]
    steps:
      - run: |
          if [ ${{ github.event_name }} == pull_request ] &amp;&amp; [ ${{ join(github.event.pull_request.labels.*.name) == &apos;&apos; }} == true ]; then
            exit 1
          elif [ ${{ (contains(needs.Build.result, &apos;failure&apos;)) }} == true ] || [ ${{ (contains(needs.UnitTests.result, &apos;failure&apos;)) }} == true ]; then
            exit 1
          else
            exit 0
          fi</code></pre><figcaption><p><span>The Code Review workflow</span></p></figcaption></figure>
<p>This is the main workflow which will be called by GitHub directly. It calls two other workflows that run the builds and tests. The last job is used for the GitHub PR status check, which fails if there is no PR label or when any of the Build / Tests jobs fail (which is needed because GitHub treats skipped jobs as failed).</p>
<p>This workflow doesn&apos;t really contain any repetition / boilerplate which can be improved. The problems lie in the two &quot;sub-workflows&quot; (Don&apos;t worry, you don&apos;t need to read the whole things):</p>
<figure class="kg-card kg-code-card"><pre><code class="language-yaml">name: Build

on:
  workflow_call:

jobs:
  Android:
    if: ${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false &amp;&amp; (contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) || contains(github.event.pull_request.labels.*.name, &apos;Android&apos;))) }}
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-java@v3
        with:
          distribution: &quot;adopt&quot;
          java-version: &quot;11&quot;

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - run: ./gradlew :androidApp:assemble

  Desktop:
    if: ${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false &amp;&amp; (contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) || contains(github.event.pull_request.labels.*.name, &apos;Desktop&apos;))) }}
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-java@v3
        with:
          distribution: &quot;adopt&quot;
          java-version: &quot;11&quot;

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - run: ./gradlew :desktopApp:assemble

  iOS:
    if: ${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false &amp;&amp; (contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) || contains(github.event.pull_request.labels.*.name, &apos;iOS&apos;))) }}
    runs-on: macos-12

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-java@v3
        with:
          distribution: &quot;adopt&quot;
          java-version: &quot;11&quot;

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - run: ./gradlew :shared:generateDummyFramework

      - name: Set up cocoapods
        uses: maxim-lobanov/setup-cocoapods@v1
        with:
          version: latest

      - name: Install Dependencies
        run: |
          cd iosApp
          pod install --verbose

      - run: xcodebuild build -workspace iosApp/iosApp.xcworkspace -configuration Debug -scheme iosApp -sdk iphoneos -destination name=&apos;iPhone 14&apos; -verbose</code></pre><figcaption><p><span>The Build workflow</span></p></figcaption></figure>
<p>The basic idea is as follows for both the Builds and UnitTests workflows. The jobs should only run when</p>
<ul><li>Started manually through <em>workflow_dispatch</em></li><li>From a push on the main branch</li><li>When a PR is opened or pushed to (It&apos;s important that the PR is not a draft as to not waste billing minutes)</li></ul>
<p>Additionally, PR labels control for which platforms the jobs will run:</p>
<figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/08/Screenshot-2023-08-28-at-18.40.02.png" class="kg-image" alt="GitHub Actions Reducing Duplication / Boilerplate" loading="lazy"></figure>
<p>For example, if the PR changes are only for the Android codebase, then the PR should have the Android label. The KMP label should be used when the shared codebase changes and because it is used by all platforms, all jobs should be run.</p>
<h3 id="boilerplate">Boilerplate</h3>
<p>In this article I&apos;ll only focus on the Build workflow, however the same steps should be applied to the UnitTests workflow, as both of them have the following boilerplate:</p>
<ul><li>All of the above jobs have the same two set-up steps<ul><li>Java</li><li>Gradle</li></ul></li><li>Repeated if statements<ul><li>For the GitHub event (<em>workflow_dispatch</em>, <em>push</em>, <em>pull_request</em>)</li><li>For the PR label (<em>github.event.pull_request.labels.*.name</em>)</li></ul></li></ul>
<h2 id="extracting-steps-to-a-separate-file">Extracting steps to a separate file</h2>
<p>Steps can be moved to a <a href="https://docs.github.com/en/actions/creating-actions/creating-a-composite-action?ref=akjaw.com">composite </a>action, which can be then re-used multiple times across all jobs.</p>
<p>The Java and Gradle composite action could look like this:</p>
<figure class="kg-card kg-code-card"><pre><code class="language-yaml">name: Job set up
description: Sets up Java and Gradle
runs:
  using: &quot;composite&quot;
  steps:
    - uses: actions/setup-java@v3
      with:
        distribution: &quot;adopt&quot;
        java-version: &quot;11&quot;

    - name: Setup Gradle
      uses: gradle/gradle-build-action@v2</code></pre><figcaption><p><span>job-set-up/action.yaml</span></p></figcaption></figure>
<p>The name of the composite action becomes the name of the parent folder, which in this case is <strong><em>job-set-up</em></strong>.</p>
<p>And then it can be re-used in all the jobs like this:</p>
<pre><code class="language-yaml">Android:
    if: ${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false &amp;&amp; (contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) || contains(github.event.pull_request.labels.*.name, &apos;Android&apos;))) }}
  runs-on: ubuntu-latest

  steps:
    - uses: actions/checkout@v3

    - name: Job set up
      uses: ./.github/actions/job-set-up

    - run: ./gradlew :androidApp:assemble</code></pre>
<div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text"><p><span>Thanks to this extraction, whenever the set-up changes, only one file will need to be changed instead of going through all the jobs in all the workflows.</span></p></div></div>
<h2 id="extracting-the-github-event-condition">Extracting the GitHub event condition</h2>
<p>As shown previously, the if statement is pretty complex, which leads to it being hard to understand. To make the matter worse, it is scattered through all the jobs, making it hard to change. </p>
<p>As we all know, developers are lazy creatures, instead of changing all of them separately, they&apos;ll for sure copy and paste the if statement, which will sooner or later lead to bugs and typos.</p>
<div class="kg-card kg-callout-card kg-callout-card-red"><div class="kg-callout-text"><p><span>Unfortunately, if statements for jobs cannot be extracted to composite actions, so a different approach is needed.</span></p></div></div>
<p>What can be done is to move the if statement to an additional job that runs before Build and UnitTests workflows:</p>
<pre><code class="language-yaml"># ...

jobs:
  SetUp:
    runs-on: ubuntu-latest
    steps:
      - id: setVariables
        name: Set variables
        run: |
          if [ ${{ github.event_name }} == workflow_dispatch ] || [ ${{ github.event_name }} == push ] || ([ ${{ github.event_name }} == pull_request ] &amp;&amp; [ ${{ github.event.pull_request.draft }} == false ]); then
            exit 0
          else
            exit 1
          fi

  Build:
    needs: SetUp
    uses: ./.github/workflows/build.yml

  UnitTests:
    needs: SetUp
    uses: ./.github/workflows/test.yml
</code></pre>
<p>The <strong>SetUp</strong> job checks if the workflow was started in the right conditions, if yes then it completes successfully, allowing the <strong>Build</strong> and <strong>UnitTests</strong> workflows to start. Otherwise, <strong>SetUp</strong> fails, causing <strong>Build</strong> and <strong>UnitTests</strong> to also fail, thus reducing the duplication inside the workflows while behaving in the same way.</p>
<p>Here&apos;s what the Android build job looks after this change:</p>
<pre><code class="language-yaml">Android:
  if: ${{ github.event_name != &apos;pull_request&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; (contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) || contains(github.event.pull_request.labels.*.name, &apos;Android&apos;))) }}
  # ...</code></pre>
<p>The if statement is better but still complex, the first condition (<em>!= &apos;pull_request&apos;</em>) is required because without it, manual or main branch runs would always result in skipped jobs, but for PRs, the labels are checked just like before. </p>
<div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text"><p><span>Remember to always double-check that condition changes didn&apos;t break other types of workflow runs.</span></p></div></div>
<h3 id="reducing-label-boilerplate">Reducing Label Boilerplate</h3>
<div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-text"><p><span>The below example only shows the Android build job, but please note that these changes affect all the jobs, but were just omitted for brevity.</span></p></div></div>
<p>The previous if statement was the same for all jobs, however label logic differs between jobs. This means that the if statement cannot be completely removed, however it can be improved using some bash logic and by <a href="https://docs.github.com/en/actions/using-jobs/defining-outputs-for-jobs?ref=akjaw.com">passing variables between jobs</a>:</p>
<pre><code class="language-yaml"># ...

jobs:
  SetUp:
    runs-on: ubuntu-latest
    steps:
      - id: setVariables
        name: Set variables
        run: |
          echo &quot;shouldRunKmp=${{ contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) }}&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
          echo &quot;shouldRunAndroid=${{ contains(github.event.pull_request.labels.*.name, &apos;Android&apos;) }}&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
          # ...
    outputs:
      shouldRunKmp: ${{ steps.setVariables.outputs.shouldRunKmp }}
      shouldRunAndroid: ${{ steps.setVariables.outputs.shouldRunAndroid }}
      
  Build:
    needs: SetUp
    uses: ./.github/workflows/build.yml
    with:
      shouldRunKmp: ${{ needs.SetUp.outputs.shouldRunKmp }}
      shouldRunAndroid: ${{ needs.SetUp.outputs.shouldRunAndroid }}</code></pre>
<p>The above set-up logic reads the PR labels and passes them to other jobs, which can be used like this:</p>
<pre><code>name: Build

on:
  workflow_call:
    inputs:
      shouldRunKmp:
        required: true
        type: string
      shouldRunAndroid:
        required: true
        type: string
      # ...

jobs:
  Android:
    if: ${{ github.event_name != &apos;pull_request&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; (inputs.shouldRunKmp == &apos;true&apos; || inputs.shouldRunAndroid == &apos;true&apos;)) }}
    # ...</code></pre>
<p>This improves the if statement, but it can still be improved further. The <strong>SetUp </strong>job already has an early return based on the <em>event_name</em>, but here it is duplicated. Instead of duplicating it, the logic can be included as part of the output variables logic:</p>
<pre><code class="language-yaml">SetUp:
  runs-on: ubuntu-latest
  steps:
    - id: setVariables
      name: Set variables
      run: |
        isFromMain=${{ github.ref == &apos;refs/heads/main&apos; }}
        isManual=${{ github.event_name == &apos;workflow_dispatch&apos; }}
        hasKmpLabel=${{ contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) }}
        shouldRunKmp=false
        if $isFromMain || $isManual || $hasKmpLabel ; then
          shouldRunKmp=true
        fi
        echo &quot;shouldRunKmp=$shouldRunKmp&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
        echo &quot;shouldRunAndroid=${{ contains(github.event.pull_request.labels.*.name, &apos;Android&apos;) }}&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
        # ...
  outputs:
    shouldRunKmp: ${{ steps.setVariables.outputs.shouldRunKmp }}
    shouldRunAndroid: ${{ steps.setVariables.outputs.shouldRunAndroid }}</code></pre>
<p>In case the PR run on the main branch or started manually, the <strong>shouldRunKmp</strong> is set to true, meaning that all jobs will be started, and for PRs, the labels decide what is run.  This change, reduces the job if statements to just using the input variables:</p>
<pre><code class="language-yaml">jobs:
  Android:
    if: ${{ inputs.shouldRunKmp == &apos;true&apos; || inputs.shouldRunAndroid == &apos;true&apos; }}
  # ...</code></pre>
<div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text"><p><span>With this change, most of the logic resides in the SetUp job, which makes it easier to understand and also eliminates copy and paste errors. In the future, if the conditions change, probably only the SetUp job will need to change and all other jobs will remain unchanged.</span></p></div></div>
<h2 id="workflow-reusability">Workflow Reusability</h2>
<p>As you may have noticed, the Build and UnitTests workflows were specified as separate workflows using <a href="https://docs.github.com/en/actions/using-workflows/reusing-workflows?ref=akjaw.com"><strong>workflow_call</strong></a>. Thanks to this, they can be used in other workflows.</p>
<p>For example, if the project had a nightly verification, both of these workflows could be re-used to make sure that nothing broke the main branch. Please note, that with the above logic, the nightly would need to pass in the correct variables for the jobs to run (Or just the KMP one).</p>
<h2 id="summary">Summary</h2>
<ul><li><strong>Composite actions</strong> - help with repeated steps, like common set-up logic</li><li><strong>Set-up jobs</strong> - can contain early returns for workflow starts in the wrong conditions</li><li><strong>Output variables</strong> - can be used to reduce the number of conditions in jobs</li><li><strong>Reusable workflows</strong> - can be re-used across multiple workflows, eliminating copying and pasting logic throughout different files</li></ul>
<p>All the changes discussed in this article can be seen in <a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions/pull/8/files?ref=akjaw.com">this PR</a>.</p>
<h3 id="course">Course</h3>
<p>If you found this helpful, you might be interested in the Android Next Level course I&apos;m a co-author of. It goes more in-depth into CI/CD for Android, e.g. Code Review, Manual builds for testers, Release builds, Nightly verification and more. Currently, the course is only available in Polish, but there is a waitlist for those interested in an English version of it, so feel free to check it out if you&apos;re interested!</p>
<figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/android-next-level/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Android Next Level</div><div class="kg-bookmark-description">A course about processes outside of coding like: CI/CD, scaling your project, good git hygiene or how to cooperate with your teammates</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="GitHub Actions Reducing Duplication / Boilerplate"><span class="kg-bookmark-author">Aleksander Jaworski</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/04/XClD1wh2eOXWfFpnetcHtdSGKbtbtvt1RTfHvouR.webp" alt="GitHub Actions Reducing Duplication / Boilerplate"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Kotlin Multiplatform GitHub Actions CI Verification using Labels]]></title><description><![CDATA[Having automatic verifications is an important part of Software Development, as the project and team grows it becomes crucial that no regressions are introduced into the "main" branch by mistake.]]></description><link>https://akjaw.com/kotlin-multiplatform-github-actions-ci-verification-labels/</link><guid isPermaLink="false">639d8eac298a6e1e01fdaf3b</guid><category><![CDATA[Multiplatform]]></category><category><![CDATA[GitHub Actions]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Sat, 02 Sep 2023 11:24:59 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/09/kmp-github-actions.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2023/09/kmp-github-actions.png" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"><p>In this article, I&apos;ll show how to set up a Continuous Integration for a Kotlin Multiplatform repository. This CI will verify that the code changes don&apos;t break the main branch. The tool used for this is <a href="https://github.com/features/actions?ref=akjaw.com">GitHub Actions</a> which I use for every personal project because it is integrated with GitHub, and it is free for open source projects.</p><p>The CI verification consists of building the project and running tests both on PRs and on the main branch. However, depending on what label is selected for the PR only selected targets will be run, which saves time and costs (for paid plans).</p><p>The repository (which includes an Android, Desktop and iOS target) used in this article is available here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/AKJAW/kotlin-multiplatform-github-actions?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - AKJAW/kotlin-multiplatform-github-actions</div><div class="kg-bookmark-description">Contribute to AKJAW/kotlin-multiplatform-github-actions development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/2376ea82e2c5b5fc444f881eb6b4be2441c9775bb2d8952c812cb53a8337c6ae/AKJAW/kotlin-multiplatform-github-actions" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"></div></a></figure><p>Keep in mind that this article shows a <a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions/tree/boilerplate/.github/workflows?ref=akjaw.com">version of the repository which contains boilerplate</a>. How to remove this boilerplate is the topic of my <a href="https://akjaw.com/github-actions-reducing-duplication-boilerplate/">other article</a>.</p><h3 id="why-is-such-verification-needed">Why is such verification needed?</h3><p>Such a Continuous Integration will give us confidence, that changes made in the project don&apos;t break anything. It offloads the verification to an automated process, which doesn&apos;t forget and runs with little manual input.</p><p>If you&apos;d like to fully automate this verification, checkout out my follow-up article on setting up <a href="https://akjaw.com/kotlin-multiplatform-gha-automated-labels/">automated labels</a>.</p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text">Having automatic verifications is an important part of Software Development, as the project and team grows it becomes crucial that no regressions are introduced into the &quot;main&quot; branch by mistake.</div></div><h2 id="steps">Steps</h2><p>Before delving into the details, I just want to show the steps which will be used in the workflow. Every job contains the same set-up steps, so they will be omitted for brevity in the examples, the full code is available in the <a href="https://github.com/AKJAW/kotlin-multiplatform-github-actions/tree/boilerplate/.github/workflows?ref=akjaw.com">repository</a>.</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">steps:
  - uses: actions/checkout@v3

  - uses: actions/setup-java@v3
    with:
      distribution: &quot;adopt&quot;
      java-version: &quot;11&quot;

  - name: Setup Gradle
    uses: gradle/gradle-build-action@v2</code></pre><figcaption>The repeated set-up code</figcaption></figure><h3 id="build">Build</h3><p>The Android and Desktop jobs are pretty much the same, with only the module being different. Both can be run on Ubuntu machines (which are the <a href="https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions?ref=akjaw.com#minute-multipliers">cheapest</a>):</p><pre><code class="language-yaml">Android:
  if: ${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false) }}
  runs-on: ubuntu-latest

  steps:
    # ...
    - run: ./gradlew :androidApp:assemble
</code></pre><pre><code class="language-yaml">Desktop:
  if: ${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false) }}
  runs-on: ubuntu-latest

  steps:
    # ...
    - run: ./gradlew :desktopApp:assemble</code></pre><p>iOS requires a macOS machine and is more involved as besides Gradle it also needs to set up CocoaPods and pods installation before running the build:</p><pre><code class="language-yaml">iOS:
  if: ${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false) }}
  runs-on: macos-12

  steps:
    # ...
    - run: ./gradlew :shared:generateDummyFramework

    - name: Set up cocoapods
      uses: maxim-lobanov/setup-cocoapods@v1
      with:
        version: latest

    - name: Install Dependencies
      run: |
        cd iosApp
        pod install --verbose

    - run: xcodebuild build -workspace iosApp/iosApp.xcworkspace -configuration Debug -scheme iosApp -sdk iphoneos -destination name=&apos;iPhone 14&apos; -verbose
</code></pre><h3 id="test">Test</h3><p>These jobs are pretty much the same, the only difference being the last Gradle / Xcode command</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">./gradlew clean testDebugUnitTest -p shared/</code></pre><figcaption>KMP Android</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-bash">./gradlew clean desktopTest -p shared</code></pre><figcaption>KMP Desktop</figcaption></figure><p>Because now Macs with M1/M2 have a different architecture than Intel, they require a different command. The if statement launches the correct command depending on the system architecture (As of now every GitHub runner has Intel, but this future proofs the CI and allows for <a href="https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners?ref=akjaw.com">self-hosted runners</a> using M1).</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">if [[ $(uname -m) == &apos;arm64&apos; ]]; then ./gradlew clean iosSimulatorArm64Test -p shared/; else ./gradlew clean iosX64Test -p shared/; fi</code></pre><figcaption>KMP iOS</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-bash">./gradlew clean testDebug -p androidApp/</code></pre><figcaption>Android</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-bash">./gradlew clean jvmTest -p desktopApp/</code></pre><figcaption>Desktop</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">The Gradle<strong> -p parameter</strong> is needed if the <a href="https://akjaw.com/modularizing-a-kotlin-multiplatform-mobile-project/">project is modularized</a>. It basically runs the command for all sub-modules in the given module/directory.</div></div><figure class="kg-card kg-code-card"><pre><code class="language-bash">xcodebuild build test -workspace iosApp/iosApp.xcworkspace -configuration Debug -scheme iosApp -sdk iphoneos -destination name=&apos;iPhone 14&apos; -verbose</code></pre><figcaption>iOS</figcaption></figure><p>With native iOS tests I&apos;ve run into a problem caused by Compose Multiplatform which I was not able to solve, so for now they are just skipped.</p><figure class="kg-card kg-code-card"><pre><code>Uncaught Kotlin exception: kotlin.IllegalStateException: Metal is not supported on this system</code></pre><figcaption>The error when running the iOS tests</figcaption></figure><p>Maybe someone will be able to show me a different way of running tests which works on GHA &#x1F604; (Comment below if you have any ideas).</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-text">If you&apos;d like to learn more about Kotlin Multiplatform Testing, you can check out <a href="https://akjaw.com/tag/testing/">my other articles</a> which focus on this topic.</div></div><h2 id="code-review-workflow">Code Review Workflow</h2><p>Both the Build and UnitTests workflows are called from the main Code Review workflow:</p><pre><code class="language-yaml">name: Code review

on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:
    types: [opened, ready_for_review, synchronize]
    branches:
      - main

jobs:
  Build:
    uses: ./.github/workflows/build.yml

  UnitTests:
    uses: ./.github/workflows/test.yml</code></pre><p>This is the workflow which will be run on opened PRs and on the main branch after merging.</p><h3 id="running-the-verification-only-when-it-makes-sense">Running the verification only when it makes sense </h3><p>You may have noticed, that all the build jobs share the same if statement (along with UnitTests):</p><pre><code class="language-yaml">if: ${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false) }}</code></pre><p>This is because the jobs should only run on certain conditions</p><ul><li>When a PR is opened</li><li>When a commit is pushed (but only on the main branch or on an opened PR)</li><li>When stared manually on GitHub</li></ul><p>The jobs shouldn&apos;t run when the PR is a draft, because these PRs are still a Work In Progress, so it makes no sense to waste resources on verifying them.</p><h3 id="running-only-the-required-jobs">Running only the required jobs</h3><p>The current solution runs every target / job regardless of what was changed. This can be improved by introducing labels to the project</p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/08/Screenshot-2023-08-28-at-18.40.02.png" class="kg-image" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels" loading="lazy" width="590" height="254"></figure><p>Then these labels can be used on GHA to decide if something should be run or not:</p><pre><code class="language-yaml">Android:
  if: ${{ contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) || contains(github.event.pull_request.labels.*.name, &apos;Android&apos;)}}
  runs-on: ubuntu-latest

  steps:
    # ...
    - run: ./gradlew :androidApp:assemble    </code></pre><p>The Android build job will be only run when the PR contains either a KMP or an Android label. </p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text">Running only specified jobs speeds up the CI and also helps with reducing the costs of GitHub Actions on paid plans / private repositories. With Kotlin Multiplatform, the bulk of the costs comes from iOS, as the macOS machines cost <strong>10 times</strong> <strong>more</strong> than Ubuntu!</div></div><p>Please note that the above if statement for labels needs to be combined with the if statement from the previous section, which results in this pretty thing:</p><pre><code class="language-yaml">${{ github.event_name == &apos;workflow_dispatch&apos; || github.event_name == &apos;push&apos; || (github.event_name == &apos;pull_request&apos; &amp;&amp; github.event.pull_request.draft == false &amp;&amp; (contains(github.event.pull_request.labels.*.name, &apos;KMP&apos;) || contains(github.event.pull_request.labels.*.name, &apos;Android&apos;))) }}</code></pre><p>This can be improved, by creating a set-up job and moving the labels to variables, however this will be explained more in-depth in my next article as stated in the beginning.</p><h3 id="blocking-merging-when-the-ci-fails">Blocking merging when the CI fails</h3><p>Unfortunately, GitHub treats skipped jobs as failed, meaning that with status checks, only the KMP label would work correctly, all other labels would result in a blocked merge option, because some jobs were skipped.</p><p>This can be fixed by adding another job which runs after all other jobs complete:</p><pre><code class="language-yaml">jobs:
  SetUp: # ...
  Build: # ...
  UnitTests: # ...
  AllowMerge:
    if: always()
    runs-on: ubuntu-latest
    needs: [ Build, UnitTests ]
    steps:
      - run: |
          if [ ${{ github.event_name }} == pull_request ] &amp;&amp; [ ${{ join(github.event.pull_request.labels.*.name) == &apos;&apos; }} == true ]; then
            exit 1
          if [ ${{ (contains(needs.Build.result, &apos;failure&apos;)) }} == true ] || [ ${{ (contains(needs.UnitTests.result, &apos;failure&apos;)) }} == true ]; then
            exit 1
          else
            exit 0
          fi</code></pre><p>After this change, the GitHub repository can block merging when the AllowMerge doesn&apos;t pass.</p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/08/Screenshot-2023-08-30-at-15.08.52.png" class="kg-image" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels" loading="lazy" width="761" height="342" srcset="https://akjaw.com/content/images/size/w600/2023/08/Screenshot-2023-08-30-at-15.08.52.png 600w, https://akjaw.com/content/images/2023/08/Screenshot-2023-08-30-at-15.08.52.png 761w" sizes="(min-width: 720px) 720px"></figure><p>The first if statement ensures that any PR has at least one label, when it&apos;s missing a label, the run will fail. Thanks to this, the problem of human forgetfulness is out of the CI equation. When someone forgets to add the label, the PR won&apos;t be allowed to merge.</p><p>The second if statement verifies that there were no failures on any of the previous jobs. In case there was at least one failure, the PR will be blocked from merging. This should guarantee that no regressions can be introduced into the main branch (unless someone puts a wrong label on the PR).</p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-text">Blocking merging when the CI fails is a good way to decrease the number of issues on the main branch. It gives us confidence, that at any point in time the main branch is working and can be more or less released.</div></div><h3 id="static-analysis-tools">Static analysis tools</h3><p>Using these types of tools as part of the Code Review workflow will keep the codebase more predictable and healthy. If you&apos;re interested in this topic, checkout my article on it:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/konsist-and-ktlint-in-github-actions-continous-integration/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Adding Konsist and Ktlint to a GitHub Actions Continuous Integration</div><div class="kg-bookmark-description">Automating code and architecture checks in the Continuous Integration pipeline can significantly reduce Code Review time and prevent human forgetfulness.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/10/github-actions-konsist-ktlint-1.png" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"></div></a></figure><h2 id="extras">Extras</h2><h3 id="automated-labels">Automated Labels</h3><p>Depending on human memory isn&apos;t always the best way of doing things. Adding labels can be automated using the <a href="https://github.com/actions/labeler?ref=akjaw.com">Labeler</a> actions, to learn more click here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/kotlin-multiplatform-gha-automated-labels/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Kotlin Multiplatform GitHub Actions Automated Pull Request Labels</div><div class="kg-bookmark-description">Having automated labels makes the Code Review process much safer by preventing merges for broken Pull Requests, ensuring that the main branch is stable</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2024/03/kmp-github-actions-automatic-labeling.png" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"></div></a></figure><h3 id="gradle-caching">Gradle Caching</h3><p>Caching can be achieved using the official <a href="https://github.com/gradle/gradle-build-action?ref=akjaw.com">gradle-build-action</a>, which has a built-in caching system. The default behavior only saves the cache on the main branch, and other branches can only read from the cache.</p><p>This behavior can be customized, so the cache can be saved on multiple branches as specified in <a href="https://github.com/gradle/gradle-build-action?ref=akjaw.com#using-the-cache-read-only">the documentation</a>.</p><h3 id="concurrency">Concurrency</h3><p>A lot of time was spent optimizing the CI, to not run when not needed, e.g. when only changing Android, the iOS jobs shouldn&apos;t run. However, there is one more optimization which cancels any unneeded jobs.</p><p>By unneeded I mean a Code Review run which is not valid anymore, for example when a new commit was pushed on an opened PR. In that case, the currently running CI on the old commit should be stopped.</p><p>Fortunately, this functionality is <a href="https://docs.github.com/en/actions/using-jobs/using-concurrency?ref=akjaw.com">supported in GitHub Actions</a>:</p><pre><code class="language-yaml">name: Code review

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != &apos;refs/heads/main&apos; }}</code></pre><p>These 3 lines, will automatically cancel any jobs which will be replaced by new ones, potentially saving a lot of unnecessary &quot;billing minutes&quot;.</p><h3 id="course">Course</h3><p>I&apos;m a co-author of an Android course which goes more in-depth into CI/CD for Android like, the above Code Review, Manual builds for testers, Release builds, Nightly verification and more. Currently, the course is only available in Polish, but there is a waitlist for those interested in an English version of it, so feel free to check it out if you&apos;re interested!</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/android-next-level/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Android Next Level</div><div class="kg-bookmark-description">A course about processes outside of coding like: CI/CD, scaling your project, good git hygiene or how to cooperate with your teammates</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/04/XClD1wh2eOXWfFpnetcHtdSGKbtbtvt1RTfHvouR.webp" alt="Kotlin Multiplatform GitHub Actions CI Verification using Labels"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines]]></title><description><![CDATA[<p>This is a write-up for a talk I gave at Droidcon Berlin 2023, the video version can be found here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.droidcon.com/2023/07/31/calling-kotlin-multiplatform-coroutines-from-swift-with-the-help-of-kmp-nativecoroutines/?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Calling Kotlin Multiplatform Coroutines from Swift with the help of KMP-NativeCoroutines - droidcon</div><div class="kg-bookmark-description">The official way of using Coroutines from Swift is awkward and has a lot of limitations. These</div></div></a></figure>]]></description><link>https://akjaw.com/async-await-coroutines-in-swift-using-kmp-nativecoroutines/</link><guid isPermaLink="false">64aaf5f0c03c752ca899e3fe</guid><category><![CDATA[Multiplatform]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Coroutines]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Wed, 02 Aug 2023 13:01:12 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/07/kmp-native-coroutines.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2023/07/kmp-native-coroutines.png" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines"><p>This is a write-up for a talk I gave at Droidcon Berlin 2023, the video version can be found here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.droidcon.com/2023/07/31/calling-kotlin-multiplatform-coroutines-from-swift-with-the-help-of-kmp-nativecoroutines/?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Calling Kotlin Multiplatform Coroutines from Swift with the help of KMP-NativeCoroutines - droidcon</div><div class="kg-bookmark-description">The official way of using Coroutines from Swift is awkward and has a lot of limitations. These limitations can be addressed by creating handwritten wrappers, but that includes a lot of boilerplate.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.droidcon.com/wp-content/uploads/2021/07/favicon-300x300.png" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines"><span class="kg-bookmark-author">droidcon</span><span class="kg-bookmark-publisher">droidcon</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.droidcon.com/wp-content/uploads/2023/07/droidcon-berlin_23-speakercard-Aleksander-Jaworski.jpg" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines"></div></a></figure><p>The presentations / decks can be found here: <br><strong><a href="https://akjaw.com/assets/talks/NativeCoroutines-Async-Droidcon-Berlin.pdf">Async / Await</a></strong><br><strong><a href="https://akjaw.com/assets/talks/NativeCoroutines-Combine-Droidcon-Berlin.pdf">Combine</a></strong></p><p>TL;DR <a href="https://github.com/rickclephas/KMP-NativeCoroutines?ref=akjaw.com">KMP-NativeCoroutines</a> allows the use Native Swift solutions (Async await, Combine or RxSwift) for asynchronous Kotlin code (Suspend functions and Flows). The library is also one of the <a href="https://kotlinfoundation.org/news/grants-program-winners-23/?ref=akjaw.com">Kotlin Grant winners</a>.</p><p>The code used in the deck and in this write-up is available on GitHub, it uses the Touchlab <a href="https://github.com/touchlab/KaMPKit?ref=akjaw.com">KaMP Kit Starter</a>. The main implementation is for Async Await, but there is also a commented out Combine implementation:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/AKJAW/Swift-Coroutines-With-KMP-NativeCoroutines?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - AKJAW/Swift-Coroutines-With-KMP-NativeCoroutines</div><div class="kg-bookmark-description">Contribute to AKJAW/Swift-Coroutines-With-KMP-NativeCoroutines development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/6a82a77ba4e3bbf0e65f16745453769c3f1cd4c8e42dfe2232760bb49fb0f99f/AKJAW/Swift-Coroutines-With-KMP-NativeCoroutines" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines"></div></a></figure><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/07/1.png" width="750" height="1334" loading="lazy" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines" srcset="https://akjaw.com/content/images/size/w600/2023/07/1.png 600w, https://akjaw.com/content/images/2023/07/1.png 750w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/07/1.5.png" width="750" height="1334" loading="lazy" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines" srcset="https://akjaw.com/content/images/size/w600/2023/07/1.5.png 600w, https://akjaw.com/content/images/2023/07/1.5.png 750w" sizes="(min-width: 720px) 720px"></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/07/2.png" width="750" height="1334" loading="lazy" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines" srcset="https://akjaw.com/content/images/size/w600/2023/07/2.png 600w, https://akjaw.com/content/images/2023/07/2.png 750w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/07/3.png" width="750" height="1334" loading="lazy" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines" srcset="https://akjaw.com/content/images/size/w600/2023/07/3.png 600w, https://akjaw.com/content/images/2023/07/3.png 750w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>The App has 3 different coroutine implementations for the KaMPKit screen and also a playground screen for playing with cancellations and exceptions.</figcaption></figure><h2 id="view-model">View Model</h2><p>The starter screen which uses the three coroutine approaches calls the following ViewModel:</p><pre><code class="language-kt">class BreedViewModel() : ViewModel() {

   val breedState: StateFlow&lt;BreedViewState&gt; // Data stream

   suspend fun refreshBreeds(): Boolean // Suspend function

   fun updateBreedFavorite(breed: Breed): Job // Ordinary function
}
</code></pre><p>Which also has a super class that has access to a CoroutineScope and a function for cancelling all child coroutines:</p><pre><code class="language-kt">actual abstract class ViewModel {

   actual val viewModelScope = MainScope()

   fun clear() {
       viewModelScope.coroutineContext.cancelChildren()
   }
}</code></pre><h2 id="normal-ordinary-function">Normal / Ordinary function</h2><p>The easiest way to call a suspend function is to call a normal function, which launches a coroutine:</p><pre><code class="language-kt">fun updateBreedFavorite(breed: Breed): Job {
   return viewModelScope.launch {
       breedRepository.updateBreedFavorite(breed)
   }
}

</code></pre><p>And in Swift, it&apos;s just a normal function call:</p><pre><code class="language-swift">func onBreedFavorite(breed: Breed) {
    viewModel?.updateBreedFavorite(breed: breed)
}
</code></pre><p>However, this approach comes with drawbacks:</p><ul><li>There is no way to get a return value from it, the only way to achievie it to modify some internal state which is exposed to the UI</li><li>A CoroutineScope is required in order to launch the coroutine from Kotlin</li><li>It&apos;s not so easy to be notified when the coroutine completes</li></ul><h2 id="suspend-functions">Suspend functions</h2><p>For cases where the return value is needed or there needs to be a callback for when the suspension completes, then a suspending function needs to be called directly from Swift.</p><figure class="kg-card kg-code-card"><pre><code class="language-kt">suspend fun refreshBreeds(): Boolean {
   return try {
       breedRepository.refreshBreeds()
       true
   } catch (exception: Exception) {
       handleBreedError(exception)
       false
   }
}
</code></pre><figcaption>A suspending function which returns a value</figcaption></figure><p>If you&apos;re just interested in the best approach, skin on over to the <a href="#kmp-nativecoroutines">KMP-NativeCoroutines </a>section.</p><h3 id="official-generated-code">Official generated code</h3><p>This is the simplest form of calling suspend functions from Swift, which comes out of the box with Kotlin. However, it is also the worst one... Calling it is pretty straight forward, every suspend function will take in a lambda from Swift:</p><pre><code class="language-swift">viewModel?.refreshBreeds { wasRefreshed, error in
  print(&quot;refresh: \(wasRefreshed), \(error)&quot;)
}
</code></pre><p>However, the return value is Optional, which means that it can exist or not (like Nullable):</p><pre><code class="language-swift">refresh: Optional(1), error: nil</code></pre><p>Even though there is an error parameter, it only represents <em>CancellationExceptions</em> all <strong><strong>Other uncaught Kotlin exceptions are fatal</strong></strong> causing the App to crash:</p><figure class="kg-card kg-code-card"><pre><code class="language-objc">/**
 * @note This method converts instances of CancellationException to errors.
 * Other uncaught Kotlin exceptions are fatal.
*/
- (void)refreshBreedsWithCompletionHandler:(void (^)(SharedBoolean * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name(&quot;refreshBreeds(completionHandler:)&quot;)));
</code></pre><figcaption>The ObjC generated code that is called from Swift</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-red"><div class="kg-callout-emoji">&#x1F615;</div><div class="kg-callout-text">The Developer experience is not so nice:<br>- It forces an Optional value<br>- There is no way to cancel it<br>- Additional error handling is required</div></div><h3 id="adapter-wrapper">Adapter / Wrapper</h3><p>This approach gives more control over our suspend functions from Swift, but requires some additional boilerplate for every function. The <strong>SuspendAdapter</strong> is the class which is used from Swift to call any suspending function:</p><pre><code class="language-kt">class SuspendAdapter&lt;T : Any&gt;(
   private val scope: CoroutineScope,
   private val suspender: suspend () -&gt; T
)
</code></pre><p>In order to use the Adapter, the iOS target requires an additional ViewModel class, which delegates all calls to the common ViewModel:</p><pre><code class="language-kt">@Suppress(&quot;Unused&quot;) // It&apos;s used from Swift
class AdapterBreedViewModel {
  
  val viewModel = BreedViewModel()

  fun refreshBreeds() =
    SuspendAdapter(viewModel.scope) {
      viewModel.refreshBreeds()
    }
}
</code></pre><p>The SuspendAdapter has a <strong>subscribe</strong> function is used to launch the coroutine from Swift:</p><pre><code class="language-kt">class SuspendAdapter&lt;T : Any&gt;(
   private val scope: CoroutineScope,
   private val suspender: suspend () -&gt; T
) {

 fun subscribe(
     onSuccess: (item: T) -&gt; Unit,
     onThrow: (error: Throwable) -&gt; Unit
 ) = scope.launch {
     try {
         onSuccess(suspender())
     } catch (error: Throwable) {
         onThrow(error)
     }
 }
}
</code></pre><p>From the iOS App perspective, the contract is definitely much easier to use and understand:</p><pre><code class="language-swift">viewModel.refreshBreeds().subscribe(
    onSuccess: { value in
        print(&quot;completion \(value)&quot;)
    },
    onThrow: { error in
        print(&quot;error \(error)&quot;)
    }
)
</code></pre><p>There is also a way to integrate it with Native solutions (like Combine):</p><figure class="kg-card kg-code-card"><pre><code class="language-swift">createFuture(suspendAdapter: adapter)
    .sink { completion in
        print(&quot;completion \(completion)&quot;)
    } receiveValue: { value in
        print(&quot;recieveValue \(value)&quot;)
    }.store(in: &amp;cancellables)
</code></pre><figcaption>createFuture is a Swift helper function that creates a combine Future for the suspend call</figcaption></figure><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">&#x1F600;</div><div class="kg-callout-text">The experience is definetely a step up:<br>+ Better error handling <br>+ No Optional value <br>+ Ability to integrate with Combine or other<br>- A lot of Additional boilerplate</div></div><h3 id="kmp-nativecoroutines">KMP-NativeCoroutines</h3><p>This is my recommended approach of handling suspend functions from Swift. The library basically generates code similar to the Adapter (but a lot better) from the previous section. Additionally, it also has Swift libraries / packages which allow the use of Native Swift solutions, which in this case it will be Async/Await.</p><p>To generate the Adapter, all that needs to be done from Kotlin is to add the correct annotation:</p><pre><code class="language-kt">@NativeCoroutines
suspend fun nativeRefreshBreeds(): Boolean =
   refreshBreeds()
</code></pre><p>And from Swift, once the correct library is imported, there is access to helper functions which for this call is <strong>asyncFunction</strong> that takes in the Kotlin suspend function call:</p><pre><code class="language-swift">func refresh() async {
  let suspend = viewModel.nativeRefreshBreeds()
  do {
      let value = 
          try await asyncFunction(for: suspend)
      print(&quot;Async Success: \(value)&quot;)
  } catch {
      print(&quot;Async Failed with error: \(error)&quot;)
  }
}

</code></pre><p>There exists also another helper function <strong>asyncResult</strong> which eliminates the need for error handling because it returns enum values representing either <strong>success</strong> or <strong>error</strong> values:</p><pre><code class="language-swift">func refresh() async {
  let suspend = viewModel.nativeRefreshBreeds()
  let value = await asyncResult(for: suspend)
  switch value {
  case .success(let result):
      print(&quot;Async Success: \(result)&quot;)
  case .failure(let error):
      print(&quot;Async Failed with error: \(error)&quot;)
  }
}


</code></pre><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F929;</div><div class="kg-callout-text">+ Explicit Error handling <br>+ No Boilerplate <br>+ No Optional <br>+ Dedicated Swift packages for Combine, Async, RxSwift <br>+ Automatic cancellation support</div></div><h4 id="internals-of-the-generated-code">Internals of the generated code</h4><p>If you&apos;re interested in what the library generates, here&apos;s my own, non-complete and simplified version.</p><p>The generated Kotlin function, which is called from Swift, looks as follows:</p><pre><code class="language-kt">@ObjCName(name = &quot;nativeRefreshBreeds&quot;)
public fun BreedViewModel.nativeRefreshBreedsNative(): NativeSuspend&lt;Boolean&gt; =
    nativeSuspend(viewModelScope) { nativeRefreshBreeds() }</code></pre><p>The body of the function does a similar thing to what happened in the handwritten Adapter, it just delegates the call to the common ViewModel.</p><p>The return value <strong><a href="https://github.com/rickclephas/KMP-NativeCoroutines/blob/3126159d2c0458e5b0ead7027802cca676dbb688/kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeSuspend.kt?ref=akjaw.com">NativeSuspend</a></strong> is type alias for a lambda that takes in three other lambdas:</p><pre><code class="language-kt">typealias NativeCallback&lt;T&gt; = (T, Unit) -&gt; Unit

typealias NativeSuspend&lt;T&gt; = (
    onResult: NativeCallback&lt;T&gt;,
    onError: NativeCallback&lt;NativeError&gt;,
    onCancelled: NativeCallback&lt;NativeError&gt;
) -&gt; NativeCancellable</code></pre><p>And in the generated code ObjC it looks like this:</p><pre><code class="language-objc">@interface SharedBreedViewModel (Extensions)
- (SharedKotlinUnit *(^(^)(SharedKotlinUnit *(^)(SharedBoolean *, SharedKotlinUnit *), SharedKotlinUnit *(^)(NSError *, SharedKotlinUnit *), SharedKotlinUnit *(^)(NSError *, SharedKotlinUnit *)))(void))nativeRefreshBreeds __attribute__((swift_name(&quot;nativeRefreshBreeds()&quot;)));
@end</code></pre><p>I personally cannot decipher the code above, but fortunately the library author can and provides this on the Swift side:</p><pre><code class="language-Swift">public typealias NativeCallback&lt;T, Unit&gt; = (T, Unit) -&gt; Unit

public typealias NativeSuspend&lt;Result, Failure: Error, Unit&gt; = (
    _ onResult: @escaping NativeCallback&lt;Result, Unit&gt;,
    _ onError: @escaping NativeCallback&lt;Failure, Unit&gt;,
    _ onCancelled: @escaping NativeCallback&lt;Failure, Unit&gt;
) -&gt; NativeCancellable&lt;Unit&gt;</code></pre><p>Which is then used for the <strong><a href="https://github.com/rickclephas/KMP-NativeCoroutines/blob/3126159d2c0458e5b0ead7027802cca676dbb688/KMPNativeCoroutinesAsync/AsyncFunction.swift?ref=akjaw.com">asyncFunction</a>:</strong></p><pre><code class="language-swift">public func asyncFunction&lt;Result, Failure: Error, Unit&gt;(
    for nativeSuspend: @escaping NativeSuspend&lt;Result, Failure, Unit&gt;
) async throws -&gt; Result {
    try await AsyncFunctionTask(nativeSuspend: nativeSuspend).awaitResult()
}</code></pre><p>A lot of more internal magic is done, which allows the following Async/Await call:</p><pre><code class="language-swift">let result =
    await asyncResult(for: viewModel.nativeRefreshBreeds())</code></pre><h2 id="flows">Flows</h2><p>It is possible to collect flows using the Official way and with the Adapter as well, however I won&apos;t dive into their implementations. They are briefly shown in the <a href="https://akjaw.com/assets/talks/NativeCoroutines-Async-Droidcon-Berlin.pdf">deck</a> on page 63 and 65 respectively.</p><h3 id="kmp-nativecoroutines-1">KMP-NativeCoroutines</h3><p>The approach is the same as with suspending functions, just adding an annotation will generate the necessary code on the Kotlin/Native side:</p><pre><code class="language-kt">@NativeCoroutinesState
val nativeBreedState: StateFlow&lt;BreedViewState&gt; =
   breedState</code></pre><p>This time, the <strong>asyncSequence</strong> is used for transforming the Flow into a Sequence:</p><pre><code class="language-Swift">func activate() async {
  let nativeFlow = viewModel.nativeBreedStateFlow
  do {
    let sequence = asyncSequence(for: nativeFlow)
    for try await dogsState in sequence {
        self.loading = dogsState.isLoading
        self.breeds = dogsState.breeds
        self.error = dogsState.error
    }
  } catch {
    print(&quot;Async Failed with error: \(error)&quot;)
  }
}
</code></pre><!--kg-card-begin: html-->The above example works, however it will generate a warning on the Swift side: <code>Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.</code><!--kg-card-end: html--><p>Which is caused by the internal state updates: </p><pre><code class="language-swift">self.loading = dogsState.isLoading
self.breeds = dogsState.breeds
self.error = dogsState.error</code></pre><p>The fix for the warning is to update the state on the main thread, which shown in the next section.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F929;</div><div class="kg-callout-text">+ Integration with Native solutions <br>+ No casting needed<br>+ Automatic cancellation <br>+ Collection on Default dispatcher</div></div><h2 id="controlling-the-scope">Controlling the Scope</h2><p>To define the scope / dispatcher which KMP-NativeCoroutines uses needs to be available in the Kotlin class and have the following annotation:</p><pre><code class="language-kt">@NativeCoroutineScope
actual val viewModelScope = MainScope()
</code></pre><p><strong>MainScope</strong> is a standard library function which creates a Scope for the &quot;UI Thread&quot;:</p><pre><code class="language-kt">public fun MainScope(): CoroutineScope = 
    ContextScope(SupervisorJob() + Dispatchers.Main)</code></pre><p>Keep in mind that the <strong>@NativeCoroutineScope</strong> only controls the Kotlin side of the coroutine. </p><ul><li>In Combine, the above scope makes the Swift &quot;collection&quot; happen on the main thread. Thanks to this, the <strong>.receive(on: DispatchQueue.main)</strong> is not required.</li><li>In Async / Await, this scope won&apos;t apply to the Swift side. The thread still needs to be specified in Swift, like in the example below.</li></ul><pre><code class="language-swift">@MainActor
func activate() async { ... }
</code></pre><p>Without this <strong>@MainActor</strong> annotation, the values are &quot;collected&quot; in Kotlin using the Main thread, but once they are received by Swift, the Task is responsible for specifying the threading.</p><h2 id="exception-error-handling">Exception / Error Handling</h2><p>It&apos;s good to know how Kotlin Coroutines exceptions affect the iOS app and how to guard against them.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://akjaw.com/content/images/2023/07/exception-1.png" class="kg-image" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines" loading="lazy" width="750" height="592" srcset="https://akjaw.com/content/images/size/w600/2023/07/exception-1.png 600w, https://akjaw.com/content/images/2023/07/exception-1.png 750w" sizes="(min-width: 720px) 720px"><figcaption>The button used for testing exception handling</figcaption></figure><h3 id="suspend-functions-1">Suspend functions</h3><p>The function which will be called is as follows, it delays for 1 second and the throws an exception:</p><pre><code class="language-kt">@NativeCoroutines
suspend fun throwException() {
   delay(1000)
   throw IllegalStateException()
}
</code></pre><p>On The Swift side, it is called like this:</p><pre><code class="language-swift">print(&quot;async exception start&quot;)
do {
    let result = try await asyncFunction(for: suspend)
    print(&quot;async exception success \(result)&quot;)
} catch {
    print(&quot;async exception failure \(error)&quot;)
}
</code></pre><p>And the result is as follows:</p><pre><code class="language-swift">18:07:03 async exception start
18:07:04 async exception failure (ErrorDomain=KotlinExcepton Code=0 &quot;(null)&quot; UserInfo={KotlinException=kotlin.IllegalStateException})
</code></pre><h3 id="flows-1">Flows</h3><p>For flows, the stream emits 3 values and then throws an exception:</p><pre><code class="language-kt">@NativeCoroutines
val errorFlow: Flow&lt;Int&gt; = flow {
   repeat(3) { number -&gt;
       emit(number)
       delay(1000)
   }
   throw IllegalStateException()
}
</code></pre><pre><code class="language-swift">do {
    let sequence = asyncSequence(for: viewModel.errorFlow)
    for try await number in sequence {
        print(&quot;sequence exception n: \(number)&quot;)
    }
} catch {
    print(&quot;sequence exception error: \(error)&quot;)
}

</code></pre><pre><code class="language-swift">2023-06-28 18:14:24 sequence exception number 0
2023-06-28 18:14:25 sequence exception number 1
2023-06-28 18:14:26 sequence exception number 2
2023-06-28 18:14:27 sequence exception error 
(Error Domain=KotlinException Code=0 &quot;(null)&quot;
UserInfo={KotlinException=kotlin.IllegalStateException})
</code></pre><p>There is also an option to just ignore errors:</p><pre><code class="language-swift">func throwException() async throws {
    let sequence = asyncSequence(for: viewModel.errorFlow)
    for try await number in sequence {
        print(&quot;sequence exception n: \(number)&quot;)
    }
}
</code></pre><pre><code class="language-swift">try? await observableModel.throwException()</code></pre><h3 id="exception-handling-summary">Exception handling summary</h3><p>In Async / Await the error handling is pretty straight forward with the <strong>do catch</strong> statement so no surprises here. However, handling these Kotlin exceptions on the Swift side is pretty awkward. </p><p>I&apos;m personally not a fan of throwing exceptions (and <a href="https://elizarov.medium.com/kotlin-and-exceptions-8062f589d07?ref=akjaw.com">neither is the Kotlin team</a>), it&apos;s much better to define a value which represents the error state. Which could a null or a sealed class, both of these approaches will be a lot easier and safer to handle on the Swift side.</p><h2 id="cancellation">Cancellation</h2><p>The following Flow will be used to demonstrate cancellation on the iOS App:</p><figure class="kg-card kg-code-card"><pre><code class="language-kt">@NativeCoroutines
val numberFlow: Flow&lt;Int&gt; = flow {
   var i = 0
   while (true) {
       emit(i++)
       delay(1000)
   }
}.onEach { number -&gt;
   log.i(&quot;numberFlow onEach: $number&quot;)
}.onCompletion { throwable -&gt;
   log.i(&quot;numberFlow onCompletion: $throwable&quot;)
}
</code></pre><figcaption>Because the logs happen on the Kotlin side, we&apos;re sure that the flow actually cancels.&#xA0;</figcaption></figure><p>There are two ways of cancelling coroutines in the iOS App: </p><ul><li>Manual, which happens when a button is pressed or when the onDisappear callback is called.</li><li>Automatic when SwiftUI deems the screen as not needed (No callbacks required).</li></ul><p>I tried making the code in such a way that both these ways are possible using Async / Await. However, <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md?ref=akjaw.com#child-tasks">at the moment Swift lacks structured concurrency for Tasks</a> meaning, that only one is possible at the same time.</p><h3 id="manual">Manual</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://akjaw.com/content/images/2023/07/cancel-manual.png" class="kg-image" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines" loading="lazy" width="750" height="528" srcset="https://akjaw.com/content/images/size/w600/2023/07/cancel-manual.png 600w, https://akjaw.com/content/images/2023/07/cancel-manual.png 750w" sizes="(min-width: 720px) 720px"><figcaption>The button used for testing manual cancellation</figcaption></figure><p>In order to manually cancel the number sequence, a new Task needs to be created and assigned to a field:</p><pre><code class="language-swift">func listenToNumbers() async {
  numberTask = Task {
      let sequence =
        asyncSequence(for: viewModel.numberFlow)
      for try await number in sequence {
        self.number = number.intValue
      }
  }
}
</code></pre><p>When the button is pressed, or onDisappear happens, then the cancellation happens:</p><pre><code class="language-swfit">func cancel() {
    numberTask?.cancel()
    numberTask = nil
}
</code></pre><p>Resulting in these logs:</p><pre><code class="language-swift">2023-06-30 12:21:30 numberFlow onEach: 0
2023-06-30 12:21:31 numberFlow onEach: 1
2023-06-30 12:21:32 numberFlow onEach: 2
2023-06-30 12:21:33 numberFlow onCompletion: kotlinx.coroutines.JobCancellationException: &#x2026;
</code></pre><h3 id="automatic">Automatic</h3><p>In order for SwiftUI to automatically cancel, the screen needs to be fully closed. In the Playground screen, it is achieved by opening a new &quot;nested&quot; screen and then closing it:</p><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/07/cancel-auto-1-1.png" width="750" height="1334" loading="lazy" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines" srcset="https://akjaw.com/content/images/size/w600/2023/07/cancel-auto-1-1.png 600w, https://akjaw.com/content/images/2023/07/cancel-auto-1-1.png 750w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/07/cancel-auto-2.png" width="750" height="1334" loading="lazy" alt="Async / Await Coroutines in Swift from Kotlin Multiplatform using KMP-NativeCoroutines" srcset="https://akjaw.com/content/images/size/w600/2023/07/cancel-auto-2.png 600w, https://akjaw.com/content/images/2023/07/cancel-auto-2.png 750w" sizes="(min-width: 720px) 720px"></div></div></div></figure><p>In order to achieve this automatic cancellation, the numberTask cannot be created, because nested Tasks are not cancelled by the parent (no structured concurrency). When the async function is executed directly:</p><pre><code class="language-swift">func listenToNumbers() async {
    let sequence =
        asyncSequence(for: viewModel.numberFlow)
    for try await number in sequence {
      self.number = number.intValue
    }
}
</code></pre><p>Then SwiftUI is in control of the Task, thus allowing automatic cancellation:</p><pre><code class="language-swift">var body: some View {
    ...
}.task {
    await observableModel.listenToNumbers()
}
</code></pre><p>No additional callbacks are required (like onDisappear), because SwiftUI takes care of everything. When the class calling the coroutine is de-initialized, then the coroutine is cancelled:</p><pre><code class="language-swift">2023-03-24 12:28:13 init 0x00006000024be000
2023-03-24 12:28:13 numberFlow onEach: 0
2023-03-24 12:28:14 numberFlow onEach: 1
2023-03-24 12:28:15 numberFlow onEach: 2
2023-03-24 12:28:16 numberFlow onEach: 3
2023-03-24 12:28:16 deinit 0x00006000024be000
2023-03-24 12:28:16 numberFlow onCompletion: kotlinx.coroutines.JobCancellationException: ...
</code></pre><h3 id="cancellation-summary">Cancellation summary</h3><p>I won&apos;t go into depth into this topic, because there are <a href="https://kt.academy/article/cc-cancellation?ref=akjaw.com">better resources</a> for it. Basically correct cancellation results in:</p><ul><li>Freeing up the users resources like data transfer</li><li>Avoiding Memory leaks</li><li>Fewer unnecessary API calls</li></ul><h2 id="ending">Ending</h2><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-text">KMP-NativeCoroutines makes the iOS developers life easier and more <strong>fun</strong>.</div></div>]]></content:encoded></item><item><title><![CDATA[Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing]]></title><description><![CDATA[In Kotlin Multiplatform, writing platform-specific implementations is sometimes necessary. However, these implementations can introduce inconsistencies between platforms if not properly tested and verified. In this article, we will explore how to tackle this problem.]]></description><link>https://akjaw.com/kotlin-multiplatform-writing-platform-specific-implementations-with-contract-testing/</link><guid isPermaLink="false">6457a99ec03c752ca899e0d8</guid><category><![CDATA[Testing]]></category><category><![CDATA[Multiplatform]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Thu, 11 May 2023 15:53:39 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/05/multiplatform-platform-implementation-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2023/05/multiplatform-platform-implementation-1.png" alt="Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing"><p>Even though The Kotlin Multiplatform ecosystem is getting bigger every day, there are cases where we need to write platform specific implementations by hand. Either because it&apos;s too specific to our domain, or because of legal reasons.</p><p>In these cases, we will need to write multiple implementations ourselves. Besides the implementation, we will also need to verify that all the implementations work in the same way between platforms. Otherwise, the shared code might be unpredictable and produce different behavior between platforms.</p><p>In this article, I&apos;ll cover how to write platform specific implementations for retrieving the device language and formatting days of the week depending on this language. To verify that both implementations work correctly, I&apos;ll show how you can write one test to cover all the implementations, reducing boilerplate and increasing alignment between platforms.</p><p>The project repository is on <a href="https://github.com/AKJAW/Kotlin-Native-Implementations?ref=akjaw.com">GitHub</a>, however due to <a href="https://github.com/JetBrains/compose-multiplatform/issues/3135?ref=akjaw.com">iOS testing issues with Compose Multiplatform</a>, Compose was commented out in order to fix the tests. This <a href="https://github.com/AKJAW/Kotlin-Native-Implementations/tree/enable-compose?ref=akjaw.com">branch</a> enables the UI, but breaks tests.</p><h2 id="how-to-write-platform-specific-code">How to write platform specific code</h2><p>There are two main ways of achieving this, I&apos;ll try to summarize each briefly.</p><h3 id="expect-actual">Expect Actual</h3><p>This is the <a href="https://kotlinlang.org/docs/multiplatform-connect-to-apis.html?ref=akjaw.com">official way</a> of providing platform specific implementations, it is nice for top level helper function or data structures with common properties, but also some platform specific ones (required by the platform).</p><p>The downside of this approach is that these implementations are final, and in most cases this makes them hard to replace with a Test Double when testing.</p><h3 id="shared-interface-with-platform-specific-implementations">Shared Interface with platform specific implementations</h3><p>In this case, there is an interface which establishes a &quot;contract&quot; that the platforms need to follow through their implementation.</p><p>The platform specific implementations can be written in two ways:</p><ol><li>Using the Native programming language, (e.g. Swift implementation that is passed in from the iOS platform to the shared code)</li><li>Using Kotlin, while targeting a specific platform: Kotlin/JVM, Kotlin/Native, Kotlin/JS.</li></ol><p>The first option might be valid for complex implementations which might be hard to write using Kotlin, like Cryptography. However, this solution is harder to maintain, because it requires separate tests in Kotlin and in the Native language.</p><p>The second option requires using &quot;native APIs&quot; (like <em>platform.Foundation</em>) from Kotlin, which might be difficult. The upside is that it is easier to test and all the code resides in the shared codebase, making it easier to maintain and reason about.</p><p>Because the shared code depends on an interface, it makes it easier to replace with a Test Double when testing. Production apps use the real implementations, and the test code can use a Fake implementation. Because of this, the article will use the latter approach.</p><h2 id="providing-the-device-language">Providing the device language</h2><p>These implementations are pretty straight forward, so I won&apos;t get into the details too much. In the common code, we have an interface and a data class:</p><pre><code class="language-kotlin">data class Language(val value: String)

interface LocaleProvider {

    fun getLanguage(): Language
}</code></pre><p>And both platform implementations are one-liners:</p><pre><code class="language-Kotlin">import java.util.Locale

class AndroidLocaleProvider : LocaleProvider {

    override fun getLanguage(): Language = 
        Language(Locale.getDefault().language)
}</code></pre><pre><code class="language-kotlin">import platform.Foundation.NSLocale
import platform.Foundation.currentLocale
import platform.Foundation.languageCode

class IosLocaleProvider : LocaleProvider {

    override fun getLanguage(): Language =
        Language(NSLocale.currentLocale.languageCode)
}</code></pre><p>These simple implementations allow us to retrieve the device language based on the phone settings.</p><p>However, we still need a way of instantiating these implementations, and currently, this can&apos;t be done in the common code. We can, however, create an expect actual function which will create &quot;an instance&quot; of the interface:</p><pre><code class="language-Kotlin">expect fun createLocaleProvider(): LocaleProvider</code></pre><pre><code class="language-kotlin">actual fun createLocaleProvider(): LocaleProvider = 
    AndroidLocaleProvider()</code></pre><pre><code class="language-kotlin">actual fun createLocaleProvider(): LocaleProvider = 
    IosLocaleProvider()</code></pre><p>These functions can be used for creating the production implementation and then introducing it to the dependency graph, or for testing the production implementations.</p><h2 id="formatting-the-day-of-the-week">Formatting the day of the week</h2><p>Now that we have a way of retrieving the device language, we can format the day based on that language. &#xA0;The next implementations are a little bit more complex, but should be still easy to understand. &#xA0;Just like previously, we start out with an interface:</p><pre><code class="language-Kotlin">interface DayOfWeekNameProvider {

    fun getLongName(dayNumber: Int): String?
}

expect fun createDayOfWeekNameProvider(
    localeProvider: LocaleProvider
): DayOfWeekNameProvider</code></pre><p>The Android implementation uses the java.util.Calendar, which is pretty much what would be done in native Android apps:</p><pre><code class="language-kotlin">class AndroidDayOfWeekNameProvider(
    private val localeProvider: LocaleProvider
) : DayOfWeekNameProvider {

    override fun getLongName(dayNumber: Int): String? {
        val calendar = Calendar.getInstance()
        calendar.set(Calendar.DAY_OF_WEEK, dayNumber)
        val locale = Locale(localeProvider.getLanguage().value)
        return calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, locale)
    }
}

actual fun createDayOfWeekNameProvider(
    localeProvider: LocaleProvider
): DayOfWeekNameProvider =
    AndroidDayOfWeekNameProvider(createLocaleProvider())
</code></pre><p></p><p>The Kotlin / Native implementation is more out of the ordinary, because we need to connect the Kotlin world with the iOS world:</p><pre><code class="language-kotlin">class IosDayOfWeekNameProvider(
    private val localeProvider: LocaleProvider
) : DayOfWeekNameProvider {

    override fun getLongName(dayNumber: Int): String? {
        val dateFormatter = NSDateFormatter().apply {
            this.locale = NSLocale(localeProvider.getLanguage().value)
        }
        val index = dayNumber % 7
        return dateFormatter.weekdaySymbols[index].toString()
    }
}

actual fun createDayOfWeekNameProvider(
    localeProvider: LocaleProvider
): DayOfWeekNameProvider =
    IosDayOfWeekNameProvider(localeProvider)</code></pre><p>As you can see, both implementations are still pretty easy to understand and are fully written in Kotlin. The best part is that they work:</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/05/Screenshot-2023-05-08-at-19.14.42.png" width="399" height="176" loading="lazy" alt="Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing"></div><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/05/Screenshot-2023-05-08-at-19.08.13.png" width="317" height="206" loading="lazy" alt="Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing"></div></div></div><figcaption>English locale</figcaption></figure><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/05/Screenshot-2023-05-08-at-19.13.26.png" width="395" height="178" loading="lazy" alt="Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing"></div><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2023/05/Screenshot-2023-05-08-at-19.09.26.png" width="321" height="188" loading="lazy" alt="Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing"></div></div></div><figcaption>German locale</figcaption></figure><p>However, there is a problem with the current implementation, can you spot it...?</p><p>On start-up, the both apps show a different day of the week. For Android, day 1 is Sunday and for iOS it is Monday. If we released this app to production, the user experience would be different between platforms. Additionally, it could cause unexpected behavior if we were to save the days to a database or send them to an API.</p><h2 id="verifying-platform-implementations">Verifying platform implementations</h2><h3 id="test-class-for-each-implementation">Test class for each implementation</h3><p>The first solution for verifying these two implementations could be writing a test for each implementation. This is ok, but it has some drawbacks:</p><ul><li>More code means more maintenance. When changing something, the work is multiplied for each platform (tests + implementation).</li><li>It is easy to forget about the other platforms, without due diligence, the tests could be misaligned and pass even though the platform implementations are different</li></ul><p>Keeping in mind, we want those two implementations to behave in the same way, it would be best to have one test that verifies all the platforms. This can be achieved by writing a <strong>Contract test</strong>.</p><h3 id="contract-test-for-verifying-all-platforms">Contract test for verifying all platforms</h3><p>A Contract test is written once in commonTest, and depending on the target, it uses the corresponding implementation for that platform. A contract test for the <em>DayOfWeekNameProvider</em> can be as follows:</p><pre><code class="language-kotlin">class DayOfWeekNameProviderContractTest {

    @Test
    fun `Day number 1 is Monday`() =
        longNameTestCase(dayNumber = 1, &quot;Monday&quot;)

    @Test
    fun `Day number 2 Tuesday`() =
        longNameTestCase(dayNumber = 2, &quot;Tuesday&quot;)

    @Test
    fun `Day number 3 Wednesday`() =
        longNameTestCase(dayNumber = 3, &quot;Wednesday&quot;)

    @Test
    fun `Day number 4 Thursday`() =
        longNameTestCase(dayNumber = 4, &quot;Thursday&quot;)

    @Test
    fun `Day number 5 Friday`() =
        longNameTestCase(dayNumber = 5, &quot;Friday&quot;)

    @Test
    fun `Day number 6 Saturday`() =
        longNameTestCase(dayNumber = 6, &quot;Saturday&quot;)

    @Test
    fun `Day number 7 Sunday`() =
        longNameTestCase(dayNumber = 7, &quot;Sunday&quot;)

    private fun longNameTestCase(dayNumber: Int, expectedName: String) {
        val sut =
            createDayOfWeekNameProvider(FakeLocaleProvider(&quot;en&quot;))
        
        val result = sut.getLongName(dayNumber)
        
        assertEquals(expectedName, result)
    }
}
</code></pre><p>The above test could be improved by also verifying a different language and testing out some <a href="https://akjaw.com/members/testing-mistakes-part-3/">boundary case values</a> like -1, 0 or 8. The test also uses a testCase function, which is one way of doing <a href="https://akjaw.com/kotlin-multiplatform-parameterized-tests-and-grouping/">parameterized tests on Kotlin Multiplatform</a>.</p><p>The test results for the current implementation look like this:</p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/05/Screenshot-2023-05-11-at-12.33.15.png" class="kg-image" alt="Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing" loading="lazy" width="930" height="325" srcset="https://akjaw.com/content/images/size/w600/2023/05/Screenshot-2023-05-11-at-12.33.15.png 600w, https://akjaw.com/content/images/2023/05/Screenshot-2023-05-11-at-12.33.15.png 930w" sizes="(min-width: 720px) 720px"></figure><p>The iOS tests pass, however the Android implementation is shifted by one day, so fixing it is just a matter of adding + 1 to the day number:</p><pre><code class="language-kotlin">override fun getLongName(dayNumber: Int): String? {
    val calendar = Calendar.getInstance()
    calendar.set(Calendar.DAY_OF_WEEK, dayNumber + 1)
    val locale = Locale(localeProvider.getLanguage().value)
    return calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, locale)
}</code></pre><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2023/05/Screenshot-2023-05-11-at-15.39.01.png" class="kg-image" alt="Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing" loading="lazy" width="597" height="304"></figure><h2 id="summary">Summary</h2><p>Hopefully by now, you have an idea of how platform implementations can be created. Writing everything in Kotlin might seem awkward at first, but there are many benefits for doing it, as I hopefully laid out in this article.</p><p>Because everything is in Kotlin, we can verify all implementations with one contract test that ensure that all the implementations are aligned and work in the same expected way.</p><p>Such contract testing can be used anywhere where we have multiple implementations, like Production and Fakes or Flavor specific implementations.</p>]]></content:encoded></item><item><title><![CDATA[Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)]]></title><description><![CDATA[Automated tests are an integral part of developing software, they help catch bugs before they reach the users and save developers time by cutting down manual testing.]]></description><link>https://akjaw.com/testing-on-kotlin-multiplatform-and-strategy-to-speed-up-development/</link><guid isPermaLink="false">61a1f2c8fba33c6d2cd0e977</guid><category><![CDATA[Testing]]></category><category><![CDATA[Multiplatform]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Sat, 15 Apr 2023 06:00:00 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/04/kotlin-multiplatform-testing-updated-4.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card kg-callout-card kg-callout-card-red"><div class="kg-callout-text">This is the new and improved version of this article, the previous one with the is still available <a href="https://akjaw.com/old/old-testing-on-kotlin-multiplatform-and-strategy-to-speed-up-development/">here</a>.</div></div><img src="https://akjaw.com/content/images/2023/04/kotlin-multiplatform-testing-updated-4.png" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"><p>The main focus of Kotlin Multiplatform (KMP) is to avoid duplicating domain logic on different platforms. You write it once and reuse it on different targets.</p><p>If the shared code is broken, then all its platforms will work incorrectly and in my opinion, the best way to ensure that something works correctly is to write a <a href="https://akjaw.com/5-beginner-testing-mistakes-i-noticed-while-working-with-less-experienced-developers/">tests covering all edge cases</a>. </p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Because the KMP code base is the heart of multiple platforms, it&apos;s important that it contains as few bugs as possible.&#xA0;</div></div><p>In this article, I&apos;ll share my experience on writing tests for Kotlin Multiplatform, along with my strategy for speeding up development using tests. At <a href="https://www.footballco.com/?ref=akjaw.com">FootballCo</a> we use this strategy for our app, and we see that it helps our development cycle.</p><p>Even though the article focuses on Kotlin Multiplatform, a lot of the principles can also be applied to plain Kotlin applications or any other type of applications for that matter. Before starting, let me ask this question:</p><h3 id="why-bother-with-testing-at-all">Why bother with testing at all?</h3><p>Some engineers see tests as a waste of time, let me give some examples to change their mind.</p><ul><li><strong>Tests provide a fast feedback loop</strong>, after a couple of seconds you know if something works, or it doesn&apos;t. Verification through the UI requires building the whole app, navigating to the correct screen and performing the action. Which you can image, takes a lot more time.</li><li><strong>It&apos;s easier to catch edge cases looking at the code</strong>, a lot of the times the UI might not reflect all possible cases that might happen.</li><li>Sometimes it&apos;s hard to set-up the app in the correct state for testing (e.g. network timeout, receiving a socket). It might be possible, but <strong>setting up the correct state in tests is much faster and easier</strong>.</li><li><strong>A well written test suite is a safety net before the app is released</strong>. With a good CI set-up, regressions / bugs don&apos;t even reach the main branch because they are caught on PRs.</li><li><strong>Tests are built in documentation</strong>, which needs to reflect the actual implementation of the app. If it isn&apos;t updated, then the tests will fail.</li></ul><h2 id="the-kotlin-multiplatform-testing-ecosystem">The Kotlin Multiplatform testing ecosystem</h2><h3 id="testing-framework">Testing Framework</h3><p>Compared to the JVM, the Kotlin Multiplatform ecosystem is still relatively young. JUnit can only be used on JVM platforms, other platforms depend on the Kotlin standard library testing framework.</p><p>An alternative way for testing Kotlin Multiplatform code would be to use a different testing framework like <a href="https://kotest.io/docs/framework/framework.html?ref=akjaw.com">Kotest</a>. I don&apos;t have much experience using it, however I found it to be less reliable than writing tests using the standard testing framework (kotlin.test). For example: I was unable to run a singular test case (a function) through the IDE.</p><p>The standard testing library does lack some cool JUnit 5 features like parameterized tests or nesting, however it is possible to add them with some additional boilerplate:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/kotlin-multiplatform-parameterized-tests-and-grouping/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Kotlin Multiplatform Parameterized Tests and Grouping Using The Standard Kotlin Testing Framework</div><div class="kg-bookmark-description">Keeping Kotlin Multiplatform tests clean while using the standard kotlin.test framework</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/01/hero.png" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"></div></a></figure><h3 id="assertions">Assertions</h3><p>Kotest also has a great <a href="https://kotest.io/docs/assertions/assertions.html?ref=akjaw.com">assertion library</a> in addition to the testing framework, which works flawlessly and can be used alongside the Kotlin standard library testing framework. Another library is <a href="https://github.com/robstoll/atrium?ref=akjaw.com">Atrium</a>, however it <a href="https://github.com/robstoll/atrium/issues/450?ref=akjaw.com">doesn&apos;t support Kotlin / Native</a>.</p><h3 id="mocking">Mocking</h3><p>For a long time, Kotlin Multiplatform did not have a mocking framework, things seem to have changed because <a href="https://github.com/mockk/mockk?ref=akjaw.com">Mockk</a> now supports Kotlin Multiplatform. However, there still might be <a href="https://github.com/mockk/mockk/issues/58?ref=akjaw.com">issues</a> on the Kotlin / Native side. An alternative to using a mocking framework is writing the mock or any other test double by hand, which will be explained in more detail in the next section.</p><p>Mocking is prevalent in tests that treat a single class as a unit, so let&apos;s touch on what can be defined as a unit before diving into the Kotlin Multiplatform testing strategy.</p><div class="kg-card kg-callout-card kg-callout-card-red"><div class="kg-callout-emoji">&#x1F4AC;</div><div class="kg-callout-text">The <strong>mock</strong> keyword is overloaded and misused in a lot of places. I won&apos;t get into the reasons why, but if you&apos;re interested in learning more about it, take a look at the article below</div></div><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://martinfowler.com/articles/mocksArentStubs.html?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Mocks Aren&#x2019;t Stubs</div><div class="kg-bookmark-description">Explaining the difference between Mock Objects and Stubs (together with other forms of Test Double). Also the difference between classical and mockist styles of unit testing.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">martinfowler.com</span><span class="kg-bookmark-publisher">Martin Fowler</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://martinfowler.com/logo-sq.png" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"></div></a></figure><h2 id="definition-of-a-unit">Definition of a Unit</h2><h3 id="one-unit-one-class">One unit, one class</h3><p>On Android, a unit is usually considered one class where all of its dependencies are <strong>mocked</strong>, probably using a framework like <a href="https://site.mockito.org/?ref=akjaw.com">Mockito</a> or <a href="https://mockk.io/?ref=akjaw.com">Mockk</a>. These frameworks are really easy to use, however they can be, easily abused, <a href="https://akjaw.com/members/testing-mistakes-part-2/">which leads to brittle tests</a> that are coupled to the implementation details of the system under test. The upside is that, these types of unit tests are the easiest to write and read (Given that the number of mock logic is not that high).</p><p>Another benefit is that all the internal dependencies API (class names, function signature etc.) are more refined because they are used inside the tests (e.g. for setting up or verification) through <strong>mocks</strong>. The downside of this is that the these mocks often make refactoring harder, since changing implementation details (like for example extracting a class) will most likely break the test (because the extracted class needs to be mocked), even though the behavior of the feature did not change.</p><p>These types of tests work in isolation, which only verify that one unit (in this case, a class) works correctly. In order to verify that a group of units behave correctly together, there is a need for additional integration tests.</p><h3 id="one-unit-multiple-classes">One unit, multiple classes</h3><p>An alternative way of thinking about a unit could be a cohesive group of classes for a given feature. These tests try to use real dependencies instead of <strong>mocks</strong>, however <em>awkward, complex </em>or<em> boundary dependencies</em> (e.g. networks, persistence etc.) or are still replaced with a test double (usually written by hand instead of <strong>mocked</strong> by a framework).</p><p>The most frequent test doubles are Fakes, which resemble the real implementation but in a simpler form to allow testing (e.g. replacing a real database with an in-memory one). </p><p>There are also Stubs which are set-up before the <a href="https://akjaw.com/5-beginner-testing-mistakes-i-noticed-while-working-with-less-experienced-developers/">action (AAA)</a> allowing the system under test to use predefined values (e.g. instead of returning system time, a predefined time value is returned). </p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Having a <a href="https://akjaw.com/modularizing-a-kotlin-multiplatform-mobile-project/">modularized project</a> allows for creating a &quot;core-test&quot; module which contains all public Test Doubles. Thanks to this, they can be re-used across all other modules without having to duplicate implementations</div></div><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://martinfowler.com/bliki/TestDouble.html?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">bliki: TestDouble</div><div class="kg-bookmark-description">Test Double is generic term for fakes, mocks, stubs, dummies and spies.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">martinfowler.com</span><span class="kg-bookmark-publisher">Martin Fowler</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://martinfowler.com/logo-sq.png" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://medium.com/@june.pravin/mocking-is-not-practical-use-fakes-e30cc6eaaf4e?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Mocking is not practical&#x200A;&#x2014;&#x200A;Use fakes</div><div class="kg-bookmark-description">This article talks about the benefits fakes provide over mocks in testing software. Fakes lead to better API and readable/robust tests.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://cdn-static-1.medium.com/_/fp/icons/Medium-Avatar-500x500.svg" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"><span class="kg-bookmark-author">Medium</span><span class="kg-bookmark-publisher">Pravin Sonawane</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://miro.medium.com/max/1200/1*4qqr5y8GshK987Dvm7DgEA.jpeg" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"></div></a></figure><h2 id="my-strategy-for-testing-kotlin-multiplatform">My strategy for testing Kotlin Multiplatform</h2><p>Because mocking the Kotlin Multiplatform is far from perfect, I went the path of writing test doubles by hand. The problem with this is that if we wanted to write every Kotlin Multiplatform unit test like on Android (unit == class). We would be forced to create interfaces for every class along with a test double for it. Which would add unnecessary complexity just for testing purposes.</p><p>This is why I decided for the most part to treat a unit as a feature / behavior (group of classes). This way, there are less test doubles involved, and the system is tested in a more &quot;production&quot; like setting.</p><p>Depending on the complexity, the tests might become integration tests rather than unit tests, but in the grand scheme of things it&apos;s not that important as long as the system is properly tested.</p><h3 id="the-system-under-test">The system under test</h3><p>Most of the time, the system under test would be the public domain class that the Kotlin Multiplatform module exposes or maybe some other complex class which delegates to other classes. </p><p>If we had a feature that allowed the user to input a keyword and get a search result based on that keyword, the Contract / API could have the following signature:</p><pre><code class="language-kotlin">fun performSearch(input: String): List&lt;String&gt;</code></pre><p>This could be a function of an interface, a use case or anything else, the point is that this class has some complex logic.</p><p>Tests for this feature could look like this:</p><pre><code class="language-Kotlin">class SuccesfulSearchTest</code></pre><pre><code class="language-kotlin">class NetworkErrorSearchTest</code></pre><pre><code class="language-kotlin">class InvalidKeywordSearchTest</code></pre><p>Each test class exercise a different path that the system could take. In this case, one for a happy path, and two for unhappy paths. They could only focus the domain layer where the network API is faked, or they could also include the data layer where the real network layer is used but mocked somehow (e.g. <a href="https://akjaw.com/using-ktor-client-mock-engine-for-integration-and-ui-tests/">Ktor MockEngine</a>, <a href="https://akjaw.com/kotlin-multiplatform-testing-sqldelight-integration-ios-android/">SQLDelight In-Memory database</a>, <a href="https://akjaw.com/using-apollo-kotlin-data-builders-for-testing/">GraphQL Mock Interceptor</a>). </p><p>The keyword validation might contain a lot of edges which may be hard to test through the <strong>InvalidKeywordSearchTest</strong> which could only focus on the domain aspects of what happens on invalid keywords. All the edge cases could be tested in a separate class:</p><pre><code class="language-kotlin">class KeywordValidatorTest {

	fun `&quot;ke&quot; is invalid`()
	fun `&quot;key&quot; is valid`()
	fun `&quot; ke &quot; is invalid`()
	fun `&quot; key &quot; is valid`()
}</code></pre><p>The example above is pretty simple, however, testing the KMP &quot;public Contract / API&quot; is a good start.</p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">For complex logic that does not involve orchestrating other classes (e.g. conversions, calculations, validation), try to extract a separate class which can be tested in isolation. This way you have one granular test which covers all the complex edge cases while keeping the &quot;integration&quot; tests simpler by only caring about one or two cases for the complex logic (making sure it is called)</div></div><h3 id="test-set-up-with-object-mothers">Test set-up with Object mothers</h3><p>Because this strategy might involve creating multiple test classes, this means that the system under test and its dependencies need to be created multiple times. Repeating the same set-up boilerplate is tedious and hard to maintain because one will require change in multiple places.</p><p>To keep things DRY, <a href="https://akjaw.com/members/testing-mistakes-part-4/">object mothers</a> can be created, removing boilerplate and making the test set-up simpler:</p><pre><code class="language-Kotlin">class SuccesfulSearchTest : KoinTest {


    private lateinit api: Api
    private lateinit sut: SearchEngine

    @BeforeTest
    fun setUp() {
        api = FakeApi()
        sut = createSearchEngine(api)
    }


    // ...
}

fun createSearchEngine(
    api: SearchEngine, 
    keywordValidator: KeywordValidator = KeywordValidator()
) =
    SearchEngine(api, keywordValidator)</code></pre><p>The top level function, createSearchEngine, can be used in all the <em>SearchTests</em> for creating the system under test. An added bonus of such object mothers is that irrelevant implementation details like the <em>KeywordValidator</em> are hidden inside the test class.</p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Such object mothers can also be used for creating complex data structures like REST schemas or GraphQL queries</div></div><h3 id="test-set-up-with-koin">Test set-up with Koin</h3><p>Another way to achieve the set-up would be to use dependency injection, luckily <a href="https://insert-koin.io/?ref=akjaw.com">Koin</a> allows for easy <a href="https://insert-koin.io/docs/reference/koin-test/testing/?ref=akjaw.com">test integrations</a>, which more or less comes down to this:</p><pre><code class="language-kotlin">class SuccesfulSearchTest : KoinTest {

    private val sut: SearchEngine by inject()

    @BeforeTest
    fun setUp() {
        startKoin {
            modules(systemUnderTestModule, testDoubleModule)
        }
    }

    @AfterTest
    fun teardown() {
        stopKoin()
    }

    // ...
}</code></pre><p>The test needs a Koin module which will provide all the needed dependencies. If the Kotlin Multiplatform code base is <a href="https://akjaw.com/modularizing-a-kotlin-multiplatform-mobile-project/">modularized</a>, the <strong>systemUnderTestModule</strong> could be the public Koin module that is attached to the dependency graph (e.g. <a href="https://github.com/AKJAW/KMM-Modularization/blob/main/kmm/todos/todos-count-dependency/src/commonMain/kotlin/co/touchlab/kmm/todos/count/dependency/composition/todoCountDependencyModule.kt?ref=akjaw.com">module</a>, <a href="https://github.com/AKJAW/KMM-Modularization/blob/main/kmm/shared/src/commonMain/kotlin/co/touchlab/kampkit/Koin.kt?ref=akjaw.com">dependency graph</a>). An example test suite which uses Koin for test set-up can be found in my <strong><a href="https://github.com/AKJAW/ktor-mock-tests/tree/main/app/src/test/java/com/akjaw/ktor/mock/tests?ref=akjaw.com">ktor-mock-tests</a></strong> repository.</p><h3 id="contract-tests-for-test-doubles">Contract tests for Test Doubles</h3><p>When creating test doubles, there might be a point when they start becoming complex, just because the production code they are replacing is also complex. Writing tests for test helpers might seem unnecessary, however, how would you otherwise prove that a test double behaves like the production counterpart? Contract tests serve that exact purpose, they verify that multiple implementations behave in the same way (that their contract is preserved). </p><p>For example, the system under test uses a database to persist its data, using a real database for every test will make the tests run a lot longer. To help with this, a fake database could be written to make the tests faster. This would result in the real database being used only in one test class and the fake one in all other cases. </p><p>Let&apos;s say that real database has the following rules (contract): </p><ul><li>adding a new item updates a &quot;reactive stream&quot;</li><li>new items cannot overwrite an existing item if their id is the same</li></ul><p>The contract base test could look like this:</p><pre><code class="language-kotlin">abstract class DatabaseContractTest {
   
   abstract var sut: Dao
   
   @Test
   fun `New items are correctly added`() {
        val item = Item(1, &quot;name&quot;)
   
        sut.addItem(item)
        
        sut.items shouldContain item
   }
   
   @Test
   fun `Items with the same id are not overwritten`() {
        val existingItem = Item(1, &quot;name&quot;)
        sut.addItem(existingItem)
        val newItem = Item(1, &quot;new item&quot;)
   
        sut.addItem(newItem)
        
        assertSoftly {
            sut.items shouldNotContain newItem
            sut.items shouldContain existingItem
        }
   }
}</code></pre><p></p><p>The base class contains the tests which will be run on the implementations (real and fake database):</p><pre><code class="language-kotlin">class SqlDelightDatabaseContractTest : DatabaseContractTest() {
    override var sut: Dao = createDealDatabase()
}

class FakeDatabaseContractTest : DatabaseContractTest() {
    override var sut: Dao = createFakeDatabase()
}
</code></pre><p>This is just a trivial example to show a glimpse of what can be done with contract tests. If you&apos;d like to learn more about this, feel free to check out these resources:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://martinfowler.com/bliki/ContractTest.html?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">bliki: ContractTest</div><div class="kg-bookmark-description">Test Doubles avoid non-deterministic errors, but you need Contract Tests to ensure they remain consistent with the real services.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">martinfowler.com</span><span class="kg-bookmark-publisher">Martin Fowler</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://martinfowler.com/bliki/images/contractTest/sketch.png" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.youtube.com/watch?v=S3qItwfhtCw&amp;ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Outside-In TDD - Search Functionality 6 (The Contract Tests)</div><div class="kg-bookmark-description">In this video, we are leveraging a fake test-double to emerge contract tests that we could use later on to make sure that every new shipping implementation a...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.youtube.com/s/desktop/d3411c39/img/favicon_144x144.png" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"><span class="kg-bookmark-author">YouTube</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://i.ytimg.com/vi/S3qItwfhtCw/maxresdefault.jpg" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"></div></a></figure><p>Big shout out to <a href="https://jovmit.io/?ref=akjaw.com">Jov Mit</a> for creating so many Android related testing content which inspired this testing strategy (If you&apos;re interested in Test Driven Development, be sure to check out his insightful <a href="https://www.youtube.com/channel/UC71omjio31Esx7LytaZ2ytA?ref=akjaw.com">screencast series on YouTube</a>).</p><!--kg-card-begin: html--><div class="ml-embedded" data-form="Tt6wpS"></div><!--kg-card-end: html--><h2 id="benefits-of-the-strategy">Benefits of the Strategy</h2><h3 id="development-speed">Development speed</h3><p>The strategy I&apos;m proposing would verify the KMM feature / module correctness at a larger scale instead of focusing on verifying individual classes. This more closely resembles how the code behaves in production, which gives us more confidence that the feature will work correctly in the application. This in turn means that there is less need to actually open up the application every time.</p><p>Building applications using Kotlin Multiplatform usually takes longer than their fully native counterparts. The Android app can be built relatively fast thanks to incremental compilation on the JVM, however for iOS the story is different. Kotlin / Native compilation in itself is pretty fast, the issue arises when creating the Objective-C binary where the gradle tasks <em>linkDebugFrameworkIos</em> and <em>linkReleaseFrameworkIos</em> are called. Luckily, tests avoid that because they only compile Kotlin / Native without creating the Objective-C binary. </p><p>Ignoring the build speed issues, let&apos;s say that the build didn&apos;t take longer. Building the whole application means building all of its parts. But when we work on a feature, we typically only want to focus and verify a small portion of the entire application. <strong>Tests allow just that, verifying only a portion of the app without needing to build everything</strong>. When we&apos;re finished working on a feature, we can plug the code into the application and verify that it correctly integrates with other parts of the application.</p><h3 id="test-function-test-case-names">Test function / test case names</h3><p>Because these tests focus more on the end result of the feature rather than on implementation details of a single class, the test function names reflect the behavior of the feature. A lot of time, this behavior also represents the business requirements of the system.</p><h3 id="refactoring">Refactoring</h3><p>With this testing strategy, refactoring would be easier because the tests don&apos;t dive into the implementation details of the system under test (like mocks tend to do). They only focus on the end result, as long as the behavior remains the same*, then the tests don&apos;t care how it was achieved. </p><p>* And it should be the same, since that&apos;s what refactoring is all about.</p><h3 id="kotlin-multiplatform-threading"><s>Kotlin Multiplatform threading</s></h3><p>The <a href="https://kotlinlang.org/docs/whatsnew16.html?ref=akjaw.com#preview-of-the-new-memory-manager">new memory model</a> is the default now, so there are no limitations like in the old memory model. To read more about the old one, you can visit the <a href="https://akjaw.com/old/old-kotlin-multiplatform-parameterized-tests-and-grouping/#kotlin-multiplatform-threading">previous version of this article</a> which covers that.</p><h3 id="test-double-reusability">Test double reusability</h3><p>The last thing I want to touch on is test double reusability. To keep the code DRY, the test doubles could also be moved to a common testing module, which helps with its reusability. For example, the data layer test doubles (e.g. network or persistence) can be often reused for UI tests. An example of this can be found in my <a href="https://akjaw.com/using-ktor-client-mock-engine-for-integration-and-ui-tests/">Ktor Mock Engine article</a>, where the integration tests and UI tests use the same engine for returning predefined data (not strictly a test double, but you get the idea). The <a href="https://github.com/AKJAW/ktor-mock-tests?ref=akjaw.com">repository</a> in the article is Android only, but it can easily be applied to Kotlin Multiplatform since <a href="https://ktor.io/docs/http-client-multiplatform.html?ref=akjaw.com">Ktor</a> has great support for it.</p><h2 id="downsides-of-the-strategy">Downsides of the Strategy</h2><h3 id="test-speed">Test speed*</h3><p>The first thing I want to address is the test speed because no one wants to wait too long for the tests to complete. Tests which treat unit as a class are superfast, but only when they use normal test doubles. Mocking frameworks with all their magic take up a lot of time and make the tests much slower compared to a test double written by hand. </p><p>The test strategy I&apos;m proposing does not use any mocking framework, only test doubles written by hand. However, the tests might use a lot more production code in a single test case, which does take more time. From my experience working with these types of tests on Kotlin Multiplatform, I didn&apos;t see anything worrying about the test speed (besides Kotlin / Native taking longer). Additionally, if the KMM code base is modularized then only the tests from a given module are executed, which is a much smaller portion of the code base.</p><p>* Test speed is a subjective topic, where every one has a different opinion on it. <a href="https://www.martinfowler.com/?ref=akjaw.com">Martin Fowler</a> has an interesting article which touches on this topic:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://martinfowler.com/bliki/UnitTest.html?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">bliki: UnitTest</div><div class="kg-bookmark-description">Unit Tests are focused on small parts of a code-base, defined in regular programming tools, and fast. There is disagreement on whether units should be solitary or sociable.</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">martinfowler.com</span><span class="kg-bookmark-publisher">Martin Fowler</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://martinfowler.com/bliki/images/unitTest/sketch.png" alt="Testing on Kotlin Multiplatform and a Strategy to Speed Up Development Time (2023 Update)"></div></a></figure><h3 id="test-readability">Test readability</h3><p>As I said before, these tests tend to be more on the integration side than the unit side (depending on how you define it). This means that more dependencies are involved, and more set-up is required.</p><p>To combat this, I recommend splitting the tests into <a href="https://akjaw.com/kotlin-multiplatform-parameterized-tests-and-grouping/">multiple files,</a> each focusing on a distinctive part of the behavior. Along with <a href="https://akjaw.com/members/testing-mistakes-part-4/">object mothers,</a> the set-up boilerplate can be reduced and implementation details hidden.</p><p>To understand these tests, more internal knowledge about the system under test is required. This is a doubled edged sword because the tests are not as easy to understand, but after you understand them you&apos;ll most likely know how the system under test works along with its collaborators.</p><h3 id="hard-to-define-what-should-be-tested">Hard to define what should be tested</h3><p>Tests where unit is class are easy to write because they always focus on a single class. When a unit is a group of classes, it is hard to define what the group should be, how <em>deep</em> should the test go? </p><p>Unfortunately, there is no rule that works in every case. Every system is different, and has different business requirements. If you start noticing that the test class is becoming too big and too complex, this might be a sign that the test goes too deep. </p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">There might be a feature which just keeps growing, and the test becomes really hard to understand. In such cases, it might be good to refactor the SUT and extract some cohesive group of logic to a separate class which can be tested more granularly. Then the original test uses a Test Double for the extracted class, making it easier to understand. This does require refactoring test code, but makes the Test Suite easier to understand</div></div><h2 id="ci-cd">CI / CD</h2><p>Tests are useless when they are not executed, and it&apos;s not a good idea to rely on human memory for that. The best way would be to integrate tests into your Continuous Integration, so they are executed more frequently. </p><p>For example, tests could be run: </p><ul><li>On every PR, making sure nothing broken is merged to the main branch. </li><li>Before starting the Release process.</li><li>Once a day </li><li>All of the above combined.</li></ul><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">For Kotlin Multiplatform, it is important to execute test for all targets. Sometimes everything works on the JVM, but Kotlin Native fails (e.g. Regex, NSDate). So the best way of making sure every platform behaves in the same way is to run tests for all targets</div></div><h2 id="summary">Summary</h2><p>In my opinion, Kotlin Multiplatform should be the most heavily tested part of the whole application. It is used by multiple platforms, so it should be as bulletproof as possible.</p><p>Writing tests during development can cut down on compilation time and give confidence that any future regressions (even 5 minutes later) will be caught by the test suite.</p><p>I hope this article was informative for you, let me know what you think in the comments below!</p>]]></content:encoded></item><item><title><![CDATA[5 Beginner Testing Mistakes I Noticed While Working with Less Experienced Developers]]></title><description><![CDATA[Having a good test suite helps with catching regressions / bugs a lot faster, and gives developers more confidence when merging / releasing their product. In this series, I'll share my recommendations for improving your tests.]]></description><link>https://akjaw.com/5-beginner-testing-mistakes-i-noticed-while-working-with-less-experienced-developers/</link><guid isPermaLink="false">63c57aab298a6e1e01fdb30e</guid><category><![CDATA[Testing]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Fri, 20 Jan 2023 16:01:57 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/04/hero-part1-3.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2023/04/hero-part1-3.png" alt="5 Beginner Testing Mistakes I Noticed While Working with Less Experienced Developers"><p>I&apos;ve been a big fan of testing ever since I was introduced to it, most of the code I write is written with Test Driven Development. Having a good test suite helps with catching regressions / bugs a lot faster, and gives developers more confidence when merging / releasing their product.</p><p>That&apos;s why I encourage / expect my teammates to write tests for the important parts of the code (In our case, it&apos;s the shared KMM codebase for Android and iOS). When reviewing PRs, I sometimes see testing approaches which could be better, in this series, I&apos;ll share my recommendations for improving your tests.</p><p>A lot of these tips are related with each other, and sometimes they complement each other. However, I&apos;ll try to keep every point straight forward and make it easy to understand without dragging additional information into them.</p><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">The first part focuses on1. Arrange Act Assert2. Not creating helper functions3. Unnecessary set up / Arrangement4. Testing multiple things instead of focusing on one5. Not using explicit set-up values in tests, but depending on default or global values</div></div><p>This series was completed as part of my Members only content, here&apos;s the full list of topics which I wrote about:</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">6. Overusing mock frameworks7. Testing implementation details instead of the outcome8. Comparing whole complex data structures instead of only the tested fields9. Not using Soft assertions10. Repeating the SUT Algorithm in assertions instead of an explicit value11. Not focusing on boundary values12. Using &quot;private&quot; functions in tests13. Too generic test case naming14. Conditions in tests15. Regular loops instead of Parameterized / Table Tests16. Ignoring mutation testing and not verifying if the tests are covering all cases17. Creating objects in every test instead of having object mothers18. Not using explicit data structures in tests, but referencing previously created ones19. Not caring about test code quality20. Always adding tests into only one file21. Ignoring test automation in the CI process</div></div><p>These parts are available here:</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F517;</div><div class="kg-callout-text"><a href="https://akjaw.com/testing-mistakes-part-2/">Second part</a><a href="https://akjaw.com/testing-mistakes-part-3/">Third part</a><a href="https://akjaw.com/testing-mistakes-part-4/">Fourth part</a></div></div><p>If you&apos;re interested and like have access to them subscribe to my newsletter or sign up right here on my website (No passwords required).</p><p>Note: All the examples here, are written with Kotlin, however, I believe that the tips presented here, could be applied in most modern programming languages.</p><h2 id="1-not-using-the-arrange-act-assert-triple-a-convention">1.  Not using the Arrange Act Assert (Triple A) Convention</h2><p>Often times I see developers group the test code like they would on production code, i.e. based on what logic is related. In tests, however, there exists a common convention / pattern which splits the code into three distinct groups separated by a new line:</p><pre><code class="language-kotlin">@Test
fun something() {
   givenApiReturnsSuccess() // arrange / set up
   
   val result = systemUnderTest.fetchDetail(&quot;1&quot;) // act / action
   
   assertEquals(Detail(&quot;1&quot;), result) // assert / verify
}</code></pre><p>These groups don&apos;t have to be one-liners, but it would be best to keep the <strong>Act</strong> group as small as possible (preferably one line). Keeping the AAA convention in all the tests helps with understanding:</p><ul><li><strong>What set-up is needed</strong></li><li><strong>What action is taken to achieve the outcome</strong></li><li><strong>What is verified by the test.</strong></li></ul><h2 id="2-not-creating-helper-functions">2. Not creating helper functions</h2><p>Tests almost always include some boilerplate, be it set up code, verification and sometimes even the action. Helper functions help address this issue</p><ul><li>Help with test readability by hiding irrelevant implementation details. With a readable test, it is much easier to understand what the system under test does</li><li>Protects from code changes, e.g: instead of changing a parameter in every test case, it only needs to be changed in the helper function</li></ul><p>Take a look at the following test:</p><pre><code class="language-kotlin">@Test
fun storageItemsAreReturnedWithTimestamp() {
    val item1 = StorageItem(id = 1, name = &quot;&quot;)
    val item2 = StorageItem(id = 2, name = &quot;&quot;)
    fakeStorage.items = listOf(item1, item2)
    val timestamp = 123
    fakeTimestampProvider.value = timestamp

    val result = systemUnderTest.fetchFromStorage()

    val result1 = result.first { item -&gt; item.id == 1 }
    assertEquals(timestamp, result1.timestamp)
    val result2 = result.first { item -&gt; item.id == 2 }
    assertEquals(timestamp, result2.timestamp)
}</code></pre><p>In this example, we have a systemUnderTest (SUT) which retrieves items from a storage and adds the current timestamp to them. However, the code includes some boilerplate that will most likely be present in other test cases, an improved version could look like this:</p><pre><code class="language-kotlin">@Test
fun storageItemsAreReturnedWithTimestamp() {
    fakeStorage.items = listOf(createStorageItem(1), createStorageItem(2))
    val timestamp = 123
    fakeTimestampProvider.value = timestamp

    val result = systemUnderTest.fetchFromStorage()

    verifyItemHasCorrectTimestamp(result, id = 1, expectedTimestamp = timestamp)
    verifyItemHasCorrectTimestamp(result, id = 2, expectedTimestamp = timestamp)
}

private fun createStorageItem(id: String): StorageItem {
    return StorageItem(id = 1, name = &quot;&quot;)
}

private fun verifyItemHasCorrectTimestamp(
    items: List&lt;Item&gt;,
    id: Int,
    expectedTimestamp: Int
) {
    val item = result.first { item -&gt; item.id == id }
    assertEquals(expectedTimestamp, item.timestamp)
}</code></pre><p>The above code is more verbose judging just by the number of lines, however <strong>the more test cases we have, the more we benefit from such helper functions</strong>. </p><p>Other examples:</p><pre><code class="language-Kotlin">fun givenApiReturnsSuccess(id: String, result: Detail) {
   mockk { fetchDetail(id) } returns result
}

fun givenDatabaseContainsItemWithId(id: String) {
   val item = ComplexItem(id = id, name = &quot;&quot;, timestamp = 0L)
   fakeStorage.items = listOf(item)
}

fun executeAndAdvanceTime() {
   systemUnderTest.execute()
   advanceTimeBy(milliseconds = 2000)
}
</code></pre><p>One additional example I like to use is a &quot;testCase&quot; helper function. From time to time you will have a behavior which requires a lot of tests, with slightly different set-up / assertions:</p><pre><code class="language-kotlin">@Test
fun `Timestamp of 0 becomes &quot;00 00 000&quot;`() {
    formatTestCase(0, &quot;00:00:000&quot;)
}

@Test
fun `Timestamp of 500 becomes &quot;00 00 500&quot;`() {
    formatTestCase(500, &quot;00:00:500&quot;)
}

@Test
fun `Timestamp of 59999 becomes &quot;00 59 999&quot;`() {
    formatTestCase(59999, &quot;00:59:999&quot;)
}

private fun formatTestCase(
    timestamp: Long,
    expectedString: String
) {
    val result = systemUnderTest.format(timestamp)

    result shouldBe expectedString
}</code></pre><p>These types of helper functions can be even more complex, as long as they help with the readability and maintainability of the test.</p><p>However, there is one important point I&apos;d like to address before going to the next point. <strong>Don&apos;t force out these helper functions, as the name suggests, they should help you and not make your life harder</strong>. </p><p>There are cases where it&apos;s better to leave the code inline without any helper functions. Like with everything in programming, this knowledge comes with experience, <strong>as long as the helper functions make the tests easier to understand and maintain, go for it</strong>.</p><h2 id="3-unnecessary-set-up-arrangement">3. Unnecessary set up / Arrangement</h2><p>Sometimes when creating a new test case, <strong>we copy an existing one, and change it to fit a new test case</strong>. When doing, so we sometimes copy more than we need, tests should only contain code which is relevant to that particular test case. <strong>More code in the tests, means it will be harder to understand</strong>, and sometimes even confuse the reader because some parts are irrelevant.</p><p>Imagine, we have a function which always retrieves something from a database cache (the function never calls the API):</p><pre><code class="language-kotlin">@Test
fun databaseIsUsedToRetrieveCachedValue() {
   val item = Item(...)
   givenDatabaseContainsItem(item)
   givenApiReturnsSuccess(item)
   
   val result = systemUnderTest.fetchFromStorage()
   
   assertEquals(item, result)
}</code></pre><p>The above test is confusing for two reasons: looking at the set-up <strong>we can take a guess that the action uses both the database and the API</strong>, additional confusion is added, because <strong>we&apos;re not sure if the item comes from the database or from the API</strong>. Only inspecting / debugging the production code would give us the answer.</p><p>Keeping our test code focused on only the relevant things will make our test cases easier to understand. In this case, removing the <em>givenApiReturnsSuccess</em> would reduce the confusion by a lot:</p><pre><code class="language-kotlin">@Test
fun databaseIsUsedToRetrieveCachedValue() {
   val item = Item(...)
   givenDatabaseContainsItem(item)
   
   val result = systemUnderTest.fetchFromStorage()
   
   assertEquals(item, result)
}</code></pre><h2 id="4-testing-multiple-things-instead-of-focusing-on-one">4. Testing multiple things instead of focusing on one</h2><p><strong>Most tests should focus on one particular behavior of the system under test</strong>. However, sometimes tests are created in ways that verify multiple things. At first sight it might seem beneficial, because less code should make it easier to understand, right? </p><pre><code class="language-kotlin">@Test
fun stateAfterInitializingIsCorrect() {
    givenApiReturns(listOf(1, 2, 3))

    systemUnderTest.initialize()
    
    assertEquals(false, systemUnderTest.isLoading)
    assertEquals(listOf(), systemUnderTest.selectedItems)
    assertEquals(listOf(1, 2, 3), systemUnderTest.items)
}</code></pre><p>Well not exactly, the problems arise, when the tests stop passing. Then you have to first look at the test code, and if that doesn&apos;t suffice you&apos;ll have to debug it, which is not ideal... That&apos;s why it&apos;s better to break it up into more test cases:</p><pre><code class="language-kotlin">@Test
fun isLoadingIsFalseAfterInitializing() {
    systemUnderTest.initialize()
    
    assertEquals(false, systemUnderTest.isLoading)
}

@Test
fun selectedItemsAreEmptyAfterInitializing() {
    systemUnderTest.initialize()
    
    assertEquals(listOf(), systemUnderTest.selectedItems)
}

@Test
fun itemsAreRetrievedFromTheApiOnInitialization() {
    givenApiReturns(listOf(1, 2, 3))
    
    systemUnderTest.initialize()
    
    assertEquals(listOf(1, 2, 3), systemUnderTest.items)
}</code></pre><p><strong>Having separate test cases for the verification involves more code, but the tests should be easier to understand</strong>, because they focus only on one aspect of the SUT. The benefit of that is, that if the test fails, the name should be more than enough to understand what&apos;s wrong with the SUT (As long as the test name is descriptive...)</p><p><strong>It&apos;s important to understand and separate things that are different in each test case.</strong></p><h2 id="5-not-using-explicit-set-up-values-in-tests-but-depending-on-default-or-global-values">5. Not using explicit set-up values in tests, but depending on default or global values</h2><p><strong>Tests should be as explicit as possible (while not hindering its readability).</strong></p><p>Sometimes when we define a test double, we might be tempted to put default values in order to reduce the amount of code we need to write in our tests. In my opinion this is not the best approach, <strong>test doubles should not have hidden values which make the tests pass</strong>, takes this for example:</p><pre><code class="language-kotlin">interface Config {
    val numberOfRepetitions: Int
}

class FakeConfig : Config {
    override var numberOfRepetitions: Int = 3
}

class SomeTest {
    // ...

    @Test
    fun apiCallOnFailShouldBeRepeatedTheCorrectAmountOfTimes() {
        mockApi.shouldReturnError()

        systemUnderTest.fetch()

        assertEquals(3, mockApi.fetchCallCount)
    }
}</code></pre><p>In the example above, we have a hidden default value in inside FakeConfig. This value is not set explicitly in the tests, so <strong>without delving into the implementation details, we won&apos;t know where the magic number 3 comes from</strong>. Of course, we could rename the test to be even more wordy, but it&apos;s better to let the test code speak for itself:</p><pre><code class="language-kotlin">class FakeConfig : Config {
    override var numberOfRepetitions: Int = 0
}

class SomeTest {
    ...

    @Test
    fun apiCallOnFailShouldBeRepeatedTheCorrectAmountOfTimes() {
        mockApi.shouldReturnError()
        val repetitionAmount = 3
        fakeConfig.numberOfRepetitions = repetitionAmount

        systemUnderTest.fetch()

        assertEquals(repetitionAmount, mockApi.fetchCallCount)
    }
}</code></pre><p>In my opinion, the above test is much easier to understand and the systemUnderTest behavior is much more explicit, even if it takes up more lines.</p><h3 id="if-youd-like-to-see-more">If you&apos;d like to see more:</h3><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F517;</div><div class="kg-callout-text"><a href="https://akjaw.com/testing-mistakes-part-2/">Second part</a><a href="https://akjaw.com/testing-mistakes-part-3/">Third part</a><a href="https://akjaw.com/testing-mistakes-part-4/">Fourth part</a></div></div>]]></content:encoded></item><item><title><![CDATA[Using Apollo Kotlin Data Builders for Testing]]></title><description><![CDATA[Data Builders provide an easy way for creating GraphQL response classes. In this post I'll show how they can be used to improve your tests.]]></description><link>https://akjaw.com/using-apollo-kotlin-data-builders-for-testing/</link><guid isPermaLink="false">63665007298a6e1e01fdab11</guid><category><![CDATA[Testing]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Android]]></category><category><![CDATA[GraphQL]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Wed, 09 Nov 2022 17:46:20 GMT</pubDate><media:content url="https://akjaw.com/content/images/2022/11/hero.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2022/11/hero.png" alt="Using Apollo Kotlin Data Builders for Testing"><p>All the code described in the article is available here</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/AKJAW/ApolloDataBuilders?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - AKJAW/ApolloDataBuilders</div><div class="kg-bookmark-description">Contribute to AKJAW/ApolloDataBuilders development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Using Apollo Kotlin Data Builders for Testing"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">AKJAW</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/b4c7544ce741b23e8a54b92caa8d0586e8b7b71cb93e57e293a4ef4fa5b430ed/AKJAW/ApolloDataBuilders" alt="Using Apollo Kotlin Data Builders for Testing"></div></a></figure><h2 id="api">API</h2><p>The GraphQL API used for this article is called Countries and returns simple information about continents, countries and languages.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/trevorblades/countries?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - trevorblades/countries: &#x1F30E; Public GraphQL API for information about countries</div><div class="kg-bookmark-description">&#x1F30E; Public GraphQL API for information about countries - GitHub - trevorblades/countries: &#x1F30E; Public GraphQL API for information about countries</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Using Apollo Kotlin Data Builders for Testing"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">trevorblades</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/d2173cf7fda71216d28f0c1dcfd01008c78f325bb5333f832903271e54f06c3f/trevorblades/countries" alt="Using Apollo Kotlin Data Builders for Testing"></div></a></figure><p>Sandbox: <a href="https://studio.apollographql.com/public/countries/explorer?ref=akjaw.com">https://studio.apollographql.com/public/countries/explorer</a></p><p>The Query we will be testing:</p><pre><code class="language-graphql">query Country($code: ID!) {
    country(code: $code) {
        name
        languages {
            ...LanguageFragment
        }
    }
}

fragment LanguageFragment on Language {
    name
}</code></pre><p></p><p>Response for CountryQuery(&quot;GB&quot;):</p><pre><code class="language-json">{
  &quot;data&quot;: {
    &quot;country&quot;: {
      &quot;name&quot;: &quot;United Kingdom&quot;,
      &quot;languages&quot;: [
        {
          &quot;name&quot;: &quot;English&quot;
        }
      ]
    }
  }
}</code></pre><h2 id="apollo">Apollo</h2><p>Based on the query above, Apollo generates something along these lines:</p><pre><code class="language-kotlin">public data class CountryQuery(
  public val code: String,
) : Query&lt;CountryQuery.Data&gt; {

  public data class Data(
    public val country: Country?,
  ) : Query.Data

  public data class Country(
    public val name: String,
    public val languages: List&lt;Language&gt;,
  )

  public data class Language(
    public val __typename: String,
    public val languageFragment: LanguageFragment,
  )
}

public data class LanguageFragment(
  public val name: String?,
) : Fragment.Data</code></pre><p>The Country data class can be instantiated manually</p><pre><code class="language-kotlin">CountryQuery.Country(
    name = &quot;United Kingdom&quot;,
    languages = listOf(
        CountryQuery.Language(
            __typename = &quot;Language&quot;,
            languageFragment = LanguageFragment(&quot;English&quot;)
        )
    )
)</code></pre><p>However, this involves a <strong>lot of boilerplate</strong> because there are no default values. Additionally, whenever the query changes the compilation will break (all constructor calls need to be updated)</p><p>To mitigate this, Apollo provides <a href="https://www.apollographql.com/docs/kotlin/testing/data-builders?ref=akjaw.com">Data builders</a>, which provides a nice DSL for the class creation.</p><h2 id="data-builders">Data Builders</h2><p>The main benefit of builders is that they <strong>provide default values for any known types</strong> (Unknown custom scalars will be explained later) and they don&apos;t care about fragments. Meaning that they don&apos;t break so easily when the query is updated.</p><h3 id="unit-tests">Unit tests</h3><p>Usually the Query data will be converted to a domain data structure, in this case it could be:</p><pre><code class="language-kotlin">data class Country(
    val name: String,
    val language: List&lt;Language&gt;,
)

data class Language(
    val name: String
)
</code></pre><p></p><p>Given we have a correctly implemented converter, we could have a test that <strong>verifies that the country name is correctly converted</strong>:</p><pre><code class="language-kotlin">@Test
fun `The country name is correctly converted`() {
    val schema = CountryQuery.Data {
        country = buildCountry {
            name = &quot;Poland&quot;
        }
    }.country!!

    val result = systemUnderTest.convert(schema)

    result.name shouldBe &quot;Poland&quot;
}
</code></pre><p>The data builder creates the whole Query &quot;Data&quot; that&apos;s why we need to access the nullable <em>.country!!</em> at the end. </p><p>To avoid repeating the same boilerplate every time, a helper function can be extracted:</p><pre><code class="language-kotlin">@Test
fun `The country name is correctly converted`() {
    val schema = createCountry {
        name = &quot;Poland&quot;
    }

    val result = systemUnderTest.convert(schema)

    result.name shouldBe &quot;Poland&quot;
}

private fun createCountry(
    block: CountryBuilder.() -&gt; Unit
): CountryQuery.Country =
    CountryQuery.Data {
        country = buildCountry {
            block()
        }
    }.country!!</code></pre><p></p><p>Thanks to this, the boilerplate is mitigated while still allowing the full usage like:</p><pre><code class="language-kotlin">createCountry {
    name = &quot;Poland&quot;
    languages = listOf(
        buildLanguage {
            name = &quot;Polish&quot;
        },
    )
}
</code></pre><p>The full converter test is available in the <a href="https://github.com/AKJAW/ApolloDataBuilders/blob/main/app/src/test/java/com/akjaw/apollo/builders/domain/model/CountryConverterTest.kt?ref=akjaw.com">repository</a>.</p><h3 id="custom-scalars">Custom Scalars</h3><p>When the data builder encounters a field which was not set inside the DSL, it will use a <strong>Resolver</strong> to put a default value there. For numbers, it starts at 0 and increments every time it is used, for strings the field name is used.</p><p>However, sometimes the GraphQL schema contains a type which Apollo cannot resolve</p><pre><code class="language-graphql">
&quot;&quot;&quot;
A custom scalar for presentation purposes
&quot;&quot;&quot;
scalar CustomScalar

query CountryWithCustomScalar {
    country(code: &quot;GB&quot;) {
        name
        customScalar
    }
}</code></pre><p></p><p>Because Apollo does not know what to do with this type, <strong>an error will be thrown if it is not set in the DSL</strong></p><pre><code>java.lang.IllegalStateException: Don&apos;t know how to instantiate leaf CustomScalar</code></pre><p></p><p>To help with this, we can define our own resolver</p><pre><code class="language-kotlin">class MyFakeResolver : FakeResolver {

    private val delegate = DefaultFakeResolver(__Schema.all)

    override fun resolveLeaf(context: FakeResolverContext): Any {
        return if (context.mergedField.type.leafType().name == &quot;CustomScalar&quot;) {
            return &quot;&quot;
        } else {
            delegate.resolveLeaf(context)
        }
    }

    override fun resolveListSize(context: FakeResolverContext): Int {
        return delegate.resolveListSize(context)
    }

    override fun resolveMaybeNull(context: FakeResolverContext): Boolean {
        return delegate.resolveMaybeNull(context)
    }

    override fun resolveTypename(context: FakeResolverContext): String {
        return delegate.resolveTypename(context)
    }
}
</code></pre><p>This resolver uses an empty string for every CustomScalar field and delegates to the default behavior in every other case. </p><p>Verification test:</p><pre><code class="language-kotlin">@Test
fun `Does not throw exception with resolver`() {
    shouldNotThrowAny {
        CountryWithCustomScalarQuery.Data(MyFakeResolver()) {
            country = buildCountry {
                name = &quot;Custom&quot;
            }
        }
    }
}

@Test
fun `Throws an exception without resolver`() {
    shouldThrow&lt;IllegalStateException&gt; {
        CountryWithCustomScalarQuery.Data {
            country = buildCountry {
                name = &quot;Custom&quot;
            }
        }
    }
}</code></pre><p></p><p>The Resolver and verification test can be found in the <a href="https://github.com/AKJAW/ApolloDataBuilders/blob/main/app/src/test/java/com/akjaw/apollo/builders/domain/model/CustomScalarTest.kt?ref=akjaw.com">repository</a>.</p><h3 id="integration-ui-tests">Integration / UI tests</h3><p>In cases where we want to test our logic along with the network layer, for example through <a href="https://github.com/AKJAW/ApolloDataBuilders/blob/main/app/src/test/java/com/akjaw/apollo/builders/domain/GetCountryTest.kt?ref=akjaw.com#L85">Mock HttpInterceptor</a> or <a href="https://www.apollographql.com/docs/kotlin/testing/mocking-http-responses?ref=akjaw.com">MockServerHandler</a>. The data builder can be used to provide the JSON string.</p><pre><code class="language-kotlin">object Responses {

// {&quot;data&quot;:{&quot;country&quot;:{&quot;name&quot;:&quot;United Kingdom&quot;,&quot;languages&quot;:[{&quot;__typename&quot;:&quot;Language&quot;,&quot;name&quot;:&quot;English&quot;}]}}}
    val SUCCESS =
        CountryQuery.Data {
            country = buildCountry {
                name = &quot;United Kingdom&quot;
                languages = listOf(
                    buildLanguage {
                        name = &quot;English&quot;
                    }
                )
            }
        }.toDataJson()

// {&quot;data&quot;:{&quot;country&quot;:null}}
    val SUCCESS_NULL_SCHEMA = CountryQuery.Data {
        country = null
    }.toDataJson()

    private fun Operation.Data.toDataJson() =
        &quot;&quot;&quot;{&quot;data&quot;:${this.toJsonString()}}&quot;&quot;&quot;

    private fun CountryQuery.Data.toDataJsonKmm() =
        &quot;&quot;&quot;{&quot;data&quot;:${CountryQuery_ResponseAdapter.Data.obj().toJsonString(this)}}&quot;&quot;&quot;
}
</code></pre><p>The main benefit of using the builder here is that the JSON will always be up-to-date with the latest Query changes and is more resilient to parsing issues.</p><p>Sidenote: the <strong>JVM</strong> Apollo implementation provides a generic Operation.Data.toJsonString() function however <strong>on KMM, every query requires its own function (like in the snippet above)</strong>.</p><p>The full integration test code is available <a href="https://github.com/AKJAW/ApolloDataBuilders/blob/main/app/src/test/java/com/akjaw/apollo/builders/domain/GetCountryTest.kt?ref=akjaw.com">here</a>.</p><p>Besides Apollo I&apos;ve also covered <a href="https://akjaw.com/using-ktor-client-mock-engine-for-integration-and-ui-tests/">Mocking Ktor network requests</a> and <a href="https://akjaw.com/kotlin-multiplatform-testing-sqldelight-integration-ios-android/">In-Memory SQLDelight testing</a> which might interest you. Additionally, I have an article series about <a href="https://akjaw.com/5-beginner-testing-mistakes-i-noticed-while-working-with-less-experienced-developers/">what testing mistakes you should avoid</a> if you want to improve your test suite.</p><h2 id="graphql-testing-course">GraphQL testing course</h2><p>Recently I had the pleasure of recording a couple of GraphQL testing course videos which will be a part of the <a href="https://kotlintesting.com/?ref=akjaw.com">KotlinTesting</a> Unit testing course. Unfortunately, the <strong>course is currently only available in Polish</strong>, but we&apos;re planning to expand it to a more world-wide audience. If you&apos;re interested in the English version or have any questions, please let me know.</p><p>In the course, I discuss things like:</p><ul><li>How Apollo can be used for building apps using a GraphQL API </li><li>Pros and Cons of different data creation Manual instantiation vs Builders</li><li><strong>Sanity tests</strong> for quick prototyping and learning how an API works</li><li>Verifying the system correctness through <strong>integration tests</strong> which include the <strong>network layer</strong></li><li>Different ways of creating Integration tests in Apollo</li></ul><p>All the course videos are recorded <strong>using a Test Driven Development approach</strong> which some might find interesting, along the way I also share some information about my approach to testing.</p><p>More information about the course can be found here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://webinar.kotlintesting.com/?utm_source=akjaw&amp;utm_medium=blog&amp;utm_campaign=webinar-14-11-2022"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Webinar | Praktycznie o testach na Androidzie</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://bucket.mlcdn.com/a/3299/3299626/images/b43983f7a9ba6a3e42142bcc67cc2b8032738810.png/599ab4a498798c5e565167a721cfef5454967ba9.png" alt="Using Apollo Kotlin Data Builders for Testing"><span class="kg-bookmark-author">Praktycznie o testach na Androidzie</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://bucket.mlcdn.com/a/3299/3299626/images/479c74e183582c23bed12bbe067bdf652e918d39.gif" alt="Using Apollo Kotlin Data Builders for Testing"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[My Git Workflow for IntelliJ and The Command Line]]></title><description><![CDATA[A short list of my most used Git commands  and IDE functionalities like Changelist, Interactive Rebase, Navigating to the previous branch]]></description><link>https://akjaw.com/my-git-intellij-command-line-workflow/</link><guid isPermaLink="false">60f65bb95e5e944f4dab9562</guid><category><![CDATA[Git]]></category><category><![CDATA[IDE]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Sat, 08 Oct 2022 07:16:59 GMT</pubDate><media:content url="https://akjaw.com/content/images/2022/10/hero.png" medium="image"/><content:encoded><![CDATA[<h2 id="android-studio">Android studio</h2><img src="https://akjaw.com/content/images/2022/10/hero.png" alt="My Git Workflow for IntelliJ and The Command Line"><p>I&apos;m using the non-modal commit interface, however a lot of the stuff described here should work in the modal interface too.</p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/09/Screenshot-2022-09-25-at-20.48.24.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="1074" height="244" srcset="https://akjaw.com/content/images/size/w600/2022/09/Screenshot-2022-09-25-at-20.48.24.png 600w, https://akjaw.com/content/images/size/w1000/2022/09/Screenshot-2022-09-25-at-20.48.24.png 1000w, https://akjaw.com/content/images/2022/09/Screenshot-2022-09-25-at-20.48.24.png 1074w" sizes="(min-width: 720px) 720px"></figure><h3 id="changelists-temporarily-saving-for-the-next-commit">Changelists - Temporarily saving for the next commit</h3><p>Sometimes you&apos;re deep in your code and you notice something that can be improved, or something that needs to be added. But you want to keep you commits granular, more focused on a single thing. To help with this I use Changelists:</p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/09/Screenshot-2022-09-25-at-20.52.26.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="492" height="382"></figure><ul><li>Default changelists (<strong>Changes</strong>) - The work that I want to commit now</li><li><strong>next</strong> changelist - Stuff I want to commit at a later point, it is usually a <em>TODO</em> comment, but sometimes it&apos;s even initial code changes. However beware, that keeping refactor changes in the next changelist can break compilation, because of broken imports split into two changelists</li><li><strong>remove</strong> changelist - Temporary changes which should not be commited. For example, some experiments with gradle, code, or some hacky workarounds to fix some temporary issues.</li></ul><h3 id="shelf-permamently-saving-something-for-later-like-stashing-but-in-the-ide-">Shelf - Permamently saving something for later (Like stashing but in the IDE)</h3><p>When you&apos;re in the middle of working on your code, but have to quickly look something up on the main branch, or change to another, shelving the changes might be your best option. The shelf works like the built in <em>git stash, </em>but it is friendlier to work with, it&apos;s also simmilair to the next changelist, but more permament.</p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/09/Screenshot-2022-09-25-at-21.03.27.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="528" height="360"></figure><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/09/Screenshot-2022-09-25-at-21.04.13.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="494" height="366"></figure><p>Keep in mind that they are not saved in the repository and only in the .idea directory, so they can be lost.</p><h2 id="file-history">File history</h2><p>Sometines you need to see what changes happened to a file, this is where the <em>Show History</em> function can be used. It lists all of the commits which included changes for this file.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://akjaw.com/content/images/2022/10/Screenshot-2022-10-08-at-08.56.10.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="349" height="84"><figcaption>Search everywhere window</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://akjaw.com/content/images/2022/10/Screenshot-2022-10-08-at-08.58.10.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="733" height="201" srcset="https://akjaw.com/content/images/size/w600/2022/10/Screenshot-2022-10-08-at-08.58.10.png 600w, https://akjaw.com/content/images/2022/10/Screenshot-2022-10-08-at-08.58.10.png 733w" sizes="(min-width: 720px) 720px"><figcaption>History window</figcaption></figure><h3 id="honorable-mentions-">Honorable mentions:</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.jetbrains.com/help/idea/resolve-conflicts.html?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Resolve Git conflicts | IntelliJ&#xA0;IDEA</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://resources.jetbrains.com/storage/products/jetbrains/img/icons/apple-touch-icon.png" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">IntelliJ&#xA0;IDEA Help</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://resources.jetbrains.com/storage/products/intellij-idea/img/meta/preview.png" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.jetbrains.com/help/idea/investigate-changes.html?ref=akjaw.com#annotate_blame"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Investigate changes in Git repository | IntelliJ&#xA0;IDEA</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://resources.jetbrains.com/storage/products/jetbrains/img/icons/apple-touch-icon.png" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">IntelliJ&#xA0;IDEA Help</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://resources.jetbrains.com/storage/products/intellij-idea/img/meta/preview.png" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><h3 id="generic-tips">Generic tips</h3><p>If you&apos;d like to improve your overall IDE workflow, checkout this article</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/17-intellij-ide-features-for-faster-development/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">17 IntelliJ IDEA/Android Studio shortcuts and features for a faster development cycle</div><div class="kg-bookmark-description">A list of IDE shortcuts and features that I use on an almost daily basis</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://images.unsplash.com/flagged/photo-1552911942-1f6cea756cf0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MXwxMTc3M3wwfDF8c2VhcmNofDM1fHxrZXlib2FyZHxlbnwwfHx8&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><h2 id="command-line">Command line</h2><h3 id="tl-dr">TL;DR</h3><p>My build is broken and I don&apos;t know why, get me out of here I don&apos;t care about losing my progress: &#xA0;<strong>git reset HEAD --hard</strong></p><p>I needed to quickly change branches, but I forgot what branch I was on earlier: &#xA0;<strong>git switch -</strong></p><p>I want to make a complex commit but I only have the command line: <strong>git add -i</strong></p><p>My last commit has some issues (code / commit message): <strong>git commit --amend</strong></p><p>I don&apos;t want to write so much: <strong>git config --global alias.&lt;alias_name&gt; &quot;&lt;alias_command&gt;&quot;</strong></p><h3 id="user-friendly-file-staging">User friendly file staging</h3><p></p><p><strong>git add -i</strong> interactive mode which allows adding, removing, diffing, patching and more</p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/10/Screenshot-2022-10-02-at-18.21.25.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="548" height="632"></figure><p>Interactive mode provides a much more user friendly staging management. If you don&apos;t have your IDE open and need to do a quick commit, <em><strong>add -i</strong></em> might be your best option. Besides adding there are a lot more useful features, feel free to explore them.</p><h3 id="undoing-current-or-previous-work">Undoing current or previous work</h3><p></p><p><strong>git reset HEAD</strong> - reset staging <em>&quot;I added some file to staging by accident, let me redo it&quot;</em></p><p><strong>git reset HEAD --hard</strong> - reset all staged and unstaged files <em>&quot;I want to throw away all my current changes and start from scratch&quot;</em></p><p><strong>git reset HEAD~</strong> - undo the last commit and reset staging but keep them them in the working directory <em>&quot;I want to clean up something in the last commit / I committed to the wrong branch and want to keep this change for later&quot;</em></p><p><strong>git reset HEAD~ --hard</strong> - remove the last commit <em>&quot;The last commit makes no sense, let me throw it away&quot;</em></p><p>* Beware that resetting something that was already pushed might create conflicts if someone else is working with that branch. If you are sure you&apos;re the only one, feel free to <em>force push</em>.</p><p>** <strong>HEAD~</strong> allows specifying a number, for example: <strong>git reset HEAD~3 --hard </strong>would remove the last 3 commits</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Git - Reset Demystified</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://git-scm.com/favicon.ico" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">Reset Demystified</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://git-scm.com/book/en/v2/images/reset-workflow.png" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><h3 id="looking-at-the-commit-history-searching-for-something">Looking at the commit history / Searching for something</h3><p></p><p><strong>git log -S &apos;code content&apos;</strong><em> </em>- search through commits based on code content</p><p><strong>git log -p -S &apos;code content&apos; </strong>- the same but also shows the commit diff</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://akjaw.com/content/images/2022/10/Screenshot-2022-10-02-at-18.28.47.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="507" height="274"><figcaption>result for: <strong>git log -p -S &quot;committed&quot;</strong></figcaption></figure><p><strong>git log --grep=&apos;commit message content&apos; </strong>- search through commits based on the commit message</p><p><strong>git log --pretty=oneline</strong> - shows a shortened log</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://akjaw.com/content/images/2022/10/Screenshot-2022-10-02-at-18.32.59.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="570" height="69"><figcaption>result for: <strong>git log --grep=&quot;commit&quot; --pretty=oneline</strong></figcaption></figure><h4 id="rewriting-history"><strong>Rewriting history</strong></h4><p></p><p><strong>git commit --amend - </strong><em>&quot;I want to clean up the last commit&quot;</em></p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/10/Screenshot-2022-10-02-at-18.44.36-1.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="403" height="633"></figure><p><strong>git rebase -i HEAD~n</strong> - rebase the last n commits. </p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/10/Screenshot-2022-10-02-at-18.46.19.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="607" height="548" srcset="https://akjaw.com/content/images/size/w600/2022/10/Screenshot-2022-10-02-at-18.46.19.png 600w, https://akjaw.com/content/images/2022/10/Screenshot-2022-10-02-at-18.46.19.png 607w"></figure><p>Interactive rebase is a powerful tool for cleaning up the commit history. Here are my most used features:</p><ul><li>r, reword - <em>&quot;I want to clean up a commit message further into history&quot;</em></li><li>e, edit - <em>&quot;I want to clean up some code further into history&quot;</em></li><li>s, squash - <em>&quot;The fix took 5 commits, but it would be cleaner as one. let me squash them into one singular commit&quot;</em></li><li>d, drop - &quot;<em>Some previous commit is irrelevant, and it is not referenced in newer commits, I can safely remove it</em>&quot;</li></ul><p>* Beware that rebasing or amending <em>changes</em> the commit hashes, which can cause conflicts if the work was already pushed. If you are sure you&apos;re the only one working on that branch, feel free to force push.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Git - Rewriting History</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://git-scm.com/favicon.ico" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">Rewriting History</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://git-scm.com/images/logo@2x.png" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.internalpointers.com/post/squash-commits-into-one-git?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Squash commits into one with Git</div><div class="kg-bookmark-description">A nice way to group some changes together, especially before sharing them with others.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.internalpointers.com/img/favicon-152.png" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">Internal Pointers</span><span class="kg-bookmark-publisher">Monocasual Laboratories</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.internalpointers.com/img/internalpointers-card.png" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://tekin.co.uk/2019/02/a-talk-about-revision-histories?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">A Branch in Time (a story about revision histories)</div><div class="kg-bookmark-description">A talk about software maintainability and the power of our revision histories. I gave this talk at Brighton Ruby and RubyConf in 2018 as well as RubyConf AU in 2019.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://tekin.co.uk/favicon.ico" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">tekin.co.uk</span><span class="kg-bookmark-publisher">Tekin S&#xFC;leyman</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://tekin.co.uk/images/speaking/a-branch-in-time-a-story-about-revision-histories.jpg" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://blog.mocoso.co.uk/talks/2015/01/12/telling-stories-through-your-commits/?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Telling stories through your commits</div><div class="kg-bookmark-description">A short talk sharing the key practices to make your commit history usefully document your code</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://blog.mocoso.co.uk/favicon.ico" alt="My Git Workflow for IntelliJ and The Command Line"></div></div><div class="kg-bookmark-thumbnail"><img src="https://blog.mocoso.co.uk/assets/c8dbe4-d4398eb4d5b4c485f44707e9be1696f9493fba47a7ffa1fd1eb6a9111bafb830d1b62013f0e4cee71981f2f9817f74f9a5f840326e31f2499c14bbf7c3c5b17c.png" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><h3 id="cherry-picking">Cherry picking</h3><p>There are situation where:</p><ul><li>You or a colleague did a fix for an issue that&apos;s bothering you, but the fix is on a branch which can&apos;t be yet merged</li><li>There is some really nasty merge conflicts, god knows why, but the problematic commit is in a middle of the PR</li></ul><p>This is where you can cherry-pick one or multiple commits onto a different branch. I use it rarely, but when I need it, it is really helpful.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.howtogeek.com/devops/what-does-git-cherry-pick-do-and-when-should-you-use-it/?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">What Does Git Cherry Pick Do, And When Should&#xA0;You Use It?</div><div class="kg-bookmark-description">git cherry-pick&#xA0;is a simple but powerful tool that allows you to selectively transfer commits from one branch to another. You can use it when you don&#x2019;t want to merge an entire branch into master, but would still like to include changes from a feature branch.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.howtogeek.com/public/images/192x192.png" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">How-To Geek</span><span class="kg-bookmark-publisher">Anthony Heddings</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.howtogeek.com/wp-content/uploads/csit/2019/10/e713ed70-1.png?height=200p&amp;trim=2,2,2,2" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.atlassian.com/git/tutorials/cherry-pick?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Git Cherry Pick | Atlassian Git Tutorial</div><div class="kg-bookmark-description">Learn about git cherry-pick, including usage, benefits, and examples using the cherry pick command.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://wac-cdn.atlassian.com/assets/img/favicons/gitguide/apple-touch-icon-152x152.png" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">Atlassian</span><span class="kg-bookmark-publisher">Atlassian</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://wac-cdn.atlassian.com/dam/jcr:dff99252-7ef3-446e-88c3-7a5938d05274/git%20cherry%20pick%20illo.png?cdnVersion=557" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><h4 id="branch-navigation"><strong>Branch navigation</strong></h4><p></p><ul><li><strong>git checkout &lt;existing-branch&gt;</strong></li><li><strong>git switch &lt;existing-branch&gt;</strong></li><li><strong>git checkout -b &lt;new-branch&gt;</strong> - create branch and check out</li><li><strong>git switch -c &lt;new-branch&gt;</strong> - create branch and check out</li><li>&quot;<strong>git switch -</strong>&quot;<strong> </strong>- checkout as previous branch</li></ul><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/10/Screenshot-2022-10-02-at-18.54.52.png" class="kg-image" alt="My Git Workflow for IntelliJ and The Command Line" loading="lazy" width="662" height="172" srcset="https://akjaw.com/content/images/size/w600/2022/10/Screenshot-2022-10-02-at-18.54.52.png 600w, https://akjaw.com/content/images/2022/10/Screenshot-2022-10-02-at-18.54.52.png 662w"></figure><p>Switch is basically a newer version of checkout, but with a less action-packed and confusing functionality.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.infoq.com/news/2019/08/git-2-23-switch-restore/?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Git 2.23 Adds Switch and Restore Commands</div><div class="kg-bookmark-description">Git 2.23 introduces two new commands meant to replace two common uses of git checkout: git switch to switch to a new branch after creating it if necessary, and git restore to restore changes from a given commit.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://cdn.infoq.com/statics_s2_20221004085140/apple-touch-icon.png" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">InfoQ</span><span class="kg-bookmark-publisher">Sergio De Simone</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.infoq.com/statics_s2_20221004085140/styles/static/images/logo/logo-big.jpg" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><h3 id="aliases">Aliases</h3><p></p><p><strong>git config --get-regexp alias</strong> - listing existing aliases</p><p><strong>git config --global alias</strong>.&lt;alias_name&gt; &quot;&lt;alias_command&gt;&quot; - setting an alias</p><p>My aliases:</p><pre><code>alias.ss status -s
alias.ai add -i
alias.logone log --pretty=oneline
alias.s- switch -
alias.sc switch -c</code></pre><h3 id="git-hooks">Git hooks</h3><p>Git hooks are a powerful tool for automating some part of your workflow, in my case it usually is:</p><ul><li>Formatting code using a linter before committing (<strong>pre-commit</strong> hook)</li><li>Ensuring that the commit message is formatted according to <a href="https://www.conventionalcommits.org/?ref=akjaw.com">Conventional Commits</a> guidelines (<strong>commit-msg </strong>hook)</li></ul><h2 id="my-workflow">My workflow</h2><p>Is a mix of both IDE and the command line.</p><p>IDE:</p><ul><li>Managing changed files</li><li>Diffing / Viewing changes</li><li>Committing</li><li>Resolving conflicts</li></ul><p>Command line:</p><ul><li>Pushing and Force pushing</li><li>Pulling and Fetching</li><li>Cleaning up commit history</li><li>Changing and deleting branches</li><li>Searching through commits</li><li>Resetting the workspace or the last commit</li><li>Cherry picking</li></ul><h2 id="git-book">Git book</h2><p>Most of my knowledge about how git works comes from the free Pro Git book. It contains a lot details, but you can focus only on stuff you need and then use it later as a reference when working with git.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://git-scm.com/book/en/v2?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Git - Book</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://git-scm.com/favicon.ico" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">Book</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://git-scm.com/images/progit2.png" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure><h2 id="course">Course</h2><p>If you&apos;d like to improve your git hygiene, or learn about more outside-coding processes, checkout the Android Next Level course which I&apos;m a co-author of.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/android-next-level/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Android Next Level</div><div class="kg-bookmark-description">A course about processes outside of coding like: CI/CD, scaling your project, good git hygiene or how to cooperate with your teammates</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="My Git Workflow for IntelliJ and The Command Line"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2023/04/XClD1wh2eOXWfFpnetcHtdSGKbtbtvt1RTfHvouR.webp" alt="My Git Workflow for IntelliJ and The Command Line"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Kotlin Multiplatform Parameterized Tests and Grouping Using The Standard Kotlin Testing Framework]]></title><description><![CDATA[Keeping Kotlin Multiplatform tests clean while using the standard kotlin.test framework]]></description><link>https://akjaw.com/kotlin-multiplatform-parameterized-tests-and-grouping/</link><guid isPermaLink="false">62834c5703d3b43079b91a30</guid><category><![CDATA[Testing]]></category><category><![CDATA[Multiplatform]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Mon, 11 Jul 2022 08:00:52 GMT</pubDate><media:content url="https://akjaw.com/content/images/2023/01/hero.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2023/01/hero.png" alt="Kotlin Multiplatform Parameterized Tests and Grouping Using The Standard Kotlin Testing Framework"><p>I covered the <a href="https://akjaw.com/testing-on-kotlin-multiplatform-and-strategy-to-speed-up-development/">Kotlin Multiplatform Testing ecosystem</a> before. But I&apos;ll try to summarize it briefly here.</p><p>Coming from Android, we&apos;re a little spoiled when it comes to testing frameworks. The default is <a href="https://junit.org/junit4/?ref=akjaw.com">JUnit 4</a>, but we also have <a href="https://junit.org/junit5/?ref=akjaw.com">JUnit 5</a> which are very mature frameworks. On Kotlin Multiplatform, we can&apos;t leverage JUnit features like parameterized tests and nested test classes. By default, Kotlin Multiplatorm uses the <a href="https://kotlinlang.org/api/latest/kotlin.test/?ref=akjaw.com"><a href="https://junit.org/junit5/?ref=akjaw.com">kotlin.test</a></a> framework, which unfortunately is not as feature rich as JUnit. </p><p>There are alternatives frameworks like the <a href="https://kotest.io/docs/framework/framework.html?ref=akjaw.com">Kotest</a>, which does offer parameterized tests, but has some limitations with Kotlin Multiplatform like the Kotest plugin not being able to run tests from commonTest, so they always need to be executed from the command line.</p><p>However, in this article I&apos;d like to focus on the standard testing framework, because it comes out of the box with Kotlin and has good IDE support, and also has a similar API as JUnit.</p><h2 id="parameterized-tests">Parameterized tests</h2><p>I tried to keep the test examples simple and self-explanatory, the last one is more complicated, but I hope it&apos;s still easy to follow.</p><h3 id="parsing-a-string-to-an-enum">Parsing a string to an Enum</h3><p>We have an Enum class representing Card (Suite), and an extension function that parses a string and returns the corresponding card or null.</p><pre><code class="language-kotlin">enum class Card {
    HEARTS,
    DIAMONDS,
    SPADES,
    CLUBS
}

fun String.parseToCard(): Card? =
    when (this) {
        &quot;Hearts&quot; -&gt; Card.HEARTS
        &quot;Diamonds&quot; -&gt; Card.DIAMONDS
        &quot;Spades&quot; -&gt; Card.SPADES
        &quot;Clubs&quot; -&gt; Card.CLUBS
        else -&gt; null
    }</code></pre><pre><code class="language-Kotlin">class CardParsingTest {

    @Test
    fun `&apos;Hearts&apos; is a card`() = validTestCase(&quot;Hearts&quot;, Card.HEARTS)

    @Test
    fun `&apos;Diamonds&apos; is a card`() = validTestCase(&quot;Diamonds&quot;, Card.DIAMONDS)

    @Test
    fun `&apos;Spades&apos; is a card`() = validTestCase(&quot;Spades&quot;, Card.SPADES)

    @Test
    fun `&apos;Clubs&apos; is a card`() = validTestCase(&quot;Clubs&quot;, Card.CLUBS)

    @Test
    fun `&apos;Heart&apos; is not a card`() = invalidTestCase(&quot;Heart&quot;)

    @Test
    fun `&apos;Diamons&apos; is not a card`() = invalidTestCase(&quot;Diamons&quot;)

    @Test
    fun `&apos;Spade&apos; is not a card`() = invalidTestCase(&quot;Spade&quot;)

    @Test
    fun `&apos;Club&apos; is not a card`() = invalidTestCase(&quot;Club&quot;)

    private fun validTestCase(stringCard: String, expectedCard: Card) {
        assertEquals(actual = stringCard.parseToCard(), expected = expectedCard)
    }

    private fun invalidTestCase(stringCard: String) {
        assertNull(actual = stringCard.parseToCard())
    }
}
</code></pre><p>There are two helper functions <strong>validTestCase</strong> and <strong>invalidTestCase</strong>, depending on the expected outcome the respective &quot;TestCase&quot; is called. Having one function could also work in this case, but I personally think that having this distinction makes the test class more readable.</p><h3 id="formatting-names">Formatting names</h3><p>We have a Person class which we want to format depending on the available data.</p><pre><code class="language-kotlin">data class Person(
    val firstName: String,
    val lastName: String,
    val secondName: String? = null,
    val secondLastName: String? = null
)

fun Person.formatToString(): String {
    val secondName = if (secondName == null) &quot;&quot; else &quot; $secondName&quot;
    val secondLastName = if (secondLastName == null) &quot;&quot; else &quot;-$secondLastName&quot;
   return &quot;$firstName$secondName $lastName$secondLastName&quot;
}</code></pre><pre><code class="language-kotlin">class PersonFormattingTest {

    @Test
    fun `Full name is correctly formatted`() = testCase(
        Person(firstName = &quot;John&quot;, lastName = &quot;Doe&quot;),
        &quot;John Doe&quot;
    )

    @Test
    fun `Full name with second name is correctly formatted`() = testCase(
        Person(firstName = &quot;John&quot;, secondName = &quot;Bob&quot;, lastName = &quot;Doe&quot;),
        &quot;John Bob Doe&quot;
    )

    @Test
    fun `Full name with second last name is correctly formatted`() = testCase(
        Person(firstName = &quot;John&quot;, lastName = &quot;Doe&quot;, secondLastName = &quot;Dilly&quot;),
        &quot;John Doe-Dilly&quot;
    )

    @Test
    fun `Full name with second name and last name is correctly formatted`() =
        testCase(
            Person(
                firstName = &quot;John&quot;,
                secondName = &quot;Bob&quot;,
                lastName = &quot;Doe&quot;,
                secondLastName = &quot;Dilly&quot;
            ),
            &quot;John Bob Doe-Dilly&quot;
        )

    private fun testCase(person: Person, expectedString: String) {
        assertEquals(actual = person.formatToString(), expected = expectedString)
    }
}</code></pre><h3 id="searching-through-keywords">Searching through keywords</h3><p>We have a UseCase which filters available keywords for a given query. The keywords come from a repository, which is replaced with a test double during testing.</p><pre><code class="language-kotlin">interface KeywordRepository {
    suspend fun getKeywords(): List&lt;String&gt;
}

class FakeDelayingKeywordRepository : KeywordRepository {
    var keywords: List&lt;String&gt; = emptyList()

    override suspend fun getKeywords(): List&lt;String&gt; {
        delay(500) // Simulate some I/O operation
        return keywords
    }
}

class SearchForKeyword(
    private val keywordRepository: KeywordRepository,
    private val dispatcher: CoroutineDispatcher,
) {

    sealed class SearchResult {
        object Empty : SearchResult()
        object InvalidQuery : SearchResult()
        object Error : SearchResult()
        data class Success(val keywords: List&lt;String&gt;) : SearchResult()
    }

    suspend fun execute(query: String): SearchResult = withContext(dispatcher) {
        if (query.count() &lt; 3) return@withContext InvalidQuery

        val keywords = keywordRepository.getKeywords()

        if (keywords.isEmpty()) return@withContext Error

        val matches = keywords.filter { keyword -&gt; keyword.contains(query) }
        if (matches.isEmpty()) {
            Empty
        } else {
            Success(matches)
        }
    }
}</code></pre><pre><code class="language-kotlin">class SearchTest {

    private lateinit var fakeKeywordRepository: FakeDelayingKeywordRepository
    private lateinit var dispatcher: TestDispatcher
    private lateinit var systemUnderTest: SearchForKeyword

    @BeforeTest
    fun setUp() {
        fakeKeywordRepository = FakeDelayingKeywordRepository()
        dispatcher = UnconfinedTestDispatcher()
        systemUnderTest = SearchForKeyword(fakeKeywordRepository, dispatcher)
    }

    @Test
    fun `When query is empty then InvalidQuery is returned`() =
        testCase(query = &quot;&quot;, expectedResult = InvalidQuery)

    @Test
    fun `When query has 2 characters then InvalidQuery is returned`() =
        testCase(query = &quot;fi&quot;, expectedResult = InvalidQuery)

    @Test
    fun `When query valid the expected Success result is returned`() =
        testCase(
            query = &quot;fir&quot;,
            keywords = listOf(&quot;first&quot;, &quot;second&quot;, &quot;Fire&quot;, &quot;sound&quot;),
            expectedResult = Success(listOf(&quot;first&quot;)),
        )

    @Test
    fun `When query valid but does not match any keywords then Empty is returned`() =
        testCase(
            query = &quot;asd&quot;,
            keywords = listOf(&quot;second&quot;, &quot;Fire&quot;, &quot;sound&quot;),
            expectedResult = Empty,
        )

    @Test
    fun `When query valid but keyword repository is empty then Error is returned`() =
        testCase(
            query = &quot;asd&quot;,
            keywords = emptyList(),
            expectedResult = Error,
        )

    private fun testCase(
        query: String,
        expectedResult: SearchForKeyword.SearchResult,
        keywords: List&lt;String&gt; = emptyList()
    ) =
        runTest(dispatcher) {
            fakeKeywordRepository.keywords = keywords

            val result = systemUnderTest.execute(query)

            assertEquals(actual = result, expected = expectedResult)
        }
}
</code></pre><p>This test is more complicated compared to the last ones, because it requires some <em>Arrangement, </em>before the test <em>Action</em> and <em>Assertions</em>. It also resembles more of a &quot;real&quot; thing that happens during app development. </p><p>The added benefit of having a &quot;TestCase&quot; function is that the systemUnderTest could also be created inside of it, without repeating the same boilerplate in every test. This can be helpful if a Test Double or System Under Test take in different constructor parameters depending on the test.</p><h3 id="grouping-tests-in-kotlin-multiplatform">Grouping tests in Kotlin Multiplatform</h3><p>Another topic I talked about in the introduction was JUnit 5 nested classes used for grouping test cases. This is not available in the standard Kotlin testing library, but there is another way of grouping test cases.</p><p>Instead of nested classes, they could be normal classes in different files. The problem with this is that the System Under Test creation has to be repeated in every test class. </p><p>To illustrate this problem, I refactored the Search UseCase to be more complicated by extracting out some classes:</p><pre><code class="language-kotlin">class QueryValidator {

    fun isValid(query: String): Boolean = query.count() &lt; 3
}

class KeywordFilter {

    fun execute(query: String, keywords: List&lt;String&gt;): List&lt;String&gt; {
        return keywords.filter { keyword -&gt; keyword.contains(query) }
    }
}</code></pre><p>The constructor of the UseCase is now the following:</p><pre><code class="language-kotlin">class SearchForKeyword(
    private val keywordRepository: KeywordRepository,
    private val queryValidator: QueryValidator,
    private val keywordFilter: KeywordFilter,
    private val dispatcher: CoroutineDispatcher,
)</code></pre><h3 id="system-under-test-creation">System Under Test creation</h3><p>The instantiation of the system under test is now more involved:</p><pre><code class="language-kotlin">private lateinit var keywordRepository: FakeDelayingKeywordRepository
private lateinit var dispatcher: TestDispatcher
private lateinit var systemUnderTest: SearchForKeyword

@BeforeTest
fun setUp() {
    keywordRepository = FakeDelayingKeywordRepository()
    dispatcher = UnconfinedTestDispatcher()
    systemUnderTest = SearchForKeyword(
        keywordRepository,
        QueryValidator(),
        KeywordFilter(),
        dispatcher
    )
}</code></pre><p>Repeating this in every test class will cause the test class to be more complex and harder to maintain every time a constructor parameters changes. To help with that, a helper <a href="https://martinfowler.com/bliki/ObjectMother.html?ref=akjaw.com">Object Mother</a> function can be created:</p><pre><code class="language-kotlin">fun createSearchForKeyword(
    dispatcher: CoroutineDispatcher,
    keywordRepository: KeywordRepository = FakeDelayingKeywordRepository(),
): SearchForKeyword {
    return SearchForKeyword(
        keywordRepository,
        QueryValidator(),
        KeywordFilter(),
        dispatcher
    )
}
</code></pre><p>The QueryValidator and KeywordFilter are implementation details from the perspective of the test, so their creation is hidden here. However, the dispatcher and repository are used inside the test class, so they are created in the test class and passed in here.</p><h3 id="different-test-classes">Different Test classes</h3><p>The test cases for the Search UseCase remain the same because the behavior did not change, however the test class will be split into two: <strong>SearchInvalidKeywordTest </strong>and <strong>SearchValidKeywordTest</strong></p><pre><code class="language-kotlin">class SearchInvalidKeywordTest {

    private lateinit var dispatcher: TestDispatcher
    private lateinit var systemUnderTest: SearchForKeyword

    @BeforeTest
    fun setUp() {
        dispatcher = UnconfinedTestDispatcher()
        systemUnderTest = createSearchForKeyword(dispatcher)
    }

    @Test
    fun `When query is empty then InvalidQuery is returned`() =
        testCase(query = &quot;&quot;)

    @Test
    fun `When query has 2 characters then InvalidQuery is returned`() =
        testCase(query = &quot;fi&quot;)

    private fun testCase(query: String) =
        runTest(dispatcher) {
            val result = systemUnderTest.execute(query)

            assertEquals(actual = result, expected = InvalidQuery)
        }
}
</code></pre><p>The keyword repository is removed from here, because it is not used by the tests. The default parameter in <strong>createSearchForKeyword</strong> function creates the repository.</p><pre><code class="language-kotlin">class SearchValidKeywordTest {

    private lateinit var fakeKeywordRepository: FakeDelayingKeywordRepository
    private lateinit var dispatcher: TestDispatcher
    private lateinit var systemUnderTest: SearchForKeyword

    @BeforeTest
    fun setUp() {
        fakeKeywordRepository = FakeDelayingKeywordRepository()
        dispatcher = UnconfinedTestDispatcher()
        systemUnderTest = createSearchForKeyword(dispatcher, fakeKeywordRepository)
    }

    @Test
    fun `When query valid the expected Success result is returned`() =
        testCase(
            query = &quot;fir&quot;,
            expectedResult = Success(listOf(&quot;first&quot;)),
            keywords = listOf(&quot;first&quot;, &quot;second&quot;, &quot;Fire&quot;, &quot;sound&quot;)
        )

    @Test
    fun `When query valid but does not match any keywords then Empty is returned`() =
        testCase(
            query = &quot;asd&quot;,
            expectedResult = Empty,
            keywords = listOf(&quot;second&quot;, &quot;Fire&quot;, &quot;sound&quot;)
        )

    @Test
    fun `When query valid but keyword repository is empty then Error is returned`() =
        testCase(
            query = &quot;asd&quot;,
            expectedResult = Error,
            keywords = emptyList()
        )

    private fun testCase(
        query: String,
        expectedResult: SearchForKeyword.SearchResult,
        keywords: List&lt;String&gt;
    ) =
        runTest(dispatcher) {
            fakeKeywordRepository.keywords = keywords

            val result = systemUnderTest.execute(query)

            assertEquals(actual = result, expected = expectedResult)
        }
}
</code></pre><p>In this test class, the keyword repository is a property because it is used to <em>Arrange </em>the correct return value before calling the System Under Test.</p><p>This method of grouping tests, is more verbose and complicated which for simple cases might be an overkill, but when the test cases keep growing (like when there are a lot of edge cases), this grouping is definitely better than having one 600+ line test class.</p><!--kg-card-begin: html--><div class="ml-embedded" data-form="Tt6wpS"></div><!--kg-card-end: html--><h2 id="summary">Summary</h2><p>Although the standard Kotlin testing framework lacks some JUnit features, we are able to implement them by hand. They are more verbose, but we have to work with what we got.</p><p>If you have your own strategies for writing Kotlin Multiplatform tests, feel free to share them in the comments &#x1F601;</p>]]></content:encoded></item><item><title><![CDATA[Kotlin Multiplatform In-Memory SQLDelight Database for Integration and UI Testing on iOS and Android]]></title><description><![CDATA[Databases are an integral part of mobile application development, so it's important that these features are properly tested. ]]></description><link>https://akjaw.com/kotlin-multiplatform-testing-sqldelight-integration-ios-android/</link><guid isPermaLink="false">625272af03d3b43079b917cd</guid><category><![CDATA[Multiplatform]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Sat, 11 Jun 2022 22:33:09 GMT</pubDate><media:content url="https://akjaw.com/content/images/2022/06/hero.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2022/06/hero.png" alt="Kotlin Multiplatform In-Memory SQLDelight Database for Integration and UI Testing on iOS and Android"><p>Databases are an integral part of almost every mobile application, in this post I&apos;ll cover how to use <a href="https://github.com/cashapp/sqldelight?ref=akjaw.com">SQLDelight</a> to provide an in-memory database for test environments.</p><h3 id="why-use-in-memory-databases">Why use in-memory databases?</h3><p>Tests should be predictable, and the execution order of the tests should change the test results. If we were to use a real database for tests, then this database would need to be cleared before (or after) every test case execution.</p><p>Even if a real database is cleared between tests, it is not guaranteed. &#xA0;Sometimes the test can crash, or &#xA0;be aborted, this can result in the database being persisted between test runs, which might change the outcome of the next test.</p><p>In-memory databases have the advantage of being discarded after every test case, so the next test execution should have no way of re-using an old in-memory database instance.</p><h2 id="app-features">App features</h2><p>The application shown in this article is available on <a href="https://github.com/AKJAW/sqldelight-testing?ref=akjaw.com">GitHub</a>. It has three features which we will be testing:</p><ul><li>Adding new items</li><li>Updating existing items</li><li>Showing the list of items</li></ul><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2022/06/device-2022-06-09-174549-2.png" width="1152" height="2465" loading="lazy" alt="Kotlin Multiplatform In-Memory SQLDelight Database for Integration and UI Testing on iOS and Android" srcset="https://akjaw.com/content/images/size/w600/2022/06/device-2022-06-09-174549-2.png 600w, https://akjaw.com/content/images/size/w1000/2022/06/device-2022-06-09-174549-2.png 1000w, https://akjaw.com/content/images/2022/06/device-2022-06-09-174549-2.png 1152w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2022/06/Screenshot-2022-06-09-at-17.51.17.png" width="1012" height="1936" loading="lazy" alt="Kotlin Multiplatform In-Memory SQLDelight Database for Integration and UI Testing on iOS and Android" srcset="https://akjaw.com/content/images/size/w600/2022/06/Screenshot-2022-06-09-at-17.51.17.png 600w, https://akjaw.com/content/images/size/w1000/2022/06/Screenshot-2022-06-09-at-17.51.17.png 1000w, https://akjaw.com/content/images/2022/06/Screenshot-2022-06-09-at-17.51.17.png 1012w" sizes="(min-width: 720px) 720px"></div></div></div></figure><p>The functionality is pretty basic, but it is enough to show how you can verify correct app behavior using an in-memory database.</p><h2 id="the-app-architecture">The App Architecture</h2><p>I tried keeping the code as simple as possible because it is not a main focus of the article. Basically, there are 3 UseCases corresponding to the app features:</p><ul><li>GetTimestampItems</li><li>AddTimestampItem</li><li>UpdateTimestampItem</li></ul><p>The basic idea is that the Add and Update UseCases insert / update items in the database with the current timestamp:</p><pre><code class="language-kotlin">internal class AddTimestampItem(
    private val tableQueries: TableQueries,
    private val timestampProvider: TimestampProvider,
) {

    suspend fun execute() = withContext(Dispatchers.Default) {
        tableQueries.insertItem(timestampProvider.getTimestampMilliseconds())
    }
}</code></pre><p>Getting the items boils down to retrieving them from the database and converting them to a more UI friendly format.</p><pre><code class="language-kotlin">internal class GetTimestampItems(
    private val tableQueries: TableQueries,
) {

    fun execute(): Flow&lt;List&lt;TimestampItem&gt;&gt; =
        tableQueries.selectAll()
            .asFlow()
            .mapToList()
            .map { items -&gt; ... }
	
    ...
}</code></pre><p></p><p>The two important things in this article is <strong>TableQueries</strong> and <strong>TimestampProvider</strong>. The TableQueries is an object that is generated by <a href="https://cashapp.github.io/sqldelight/multiplatform_sqlite/?ref=akjaw.com">SQLDelight</a>, in tests we will replace the production database with an in-memory one. TimestampProvider returns the current system timestamp, but in tests we will replace it with a test doubles that allows controlling the returned value (relying on real system time makes the test unpredictable).</p><h3 id="database-schema">Database Schema</h3><pre><code class="language-sql">CREATE TABLE TimestampItemEntity (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    time INTEGER NOT NULL,
    version INTEGER AS Int NOT NULL
);

selectAll:
SELECT * FROM TimestampItemEntity;

insertItem:
INSERT OR IGNORE INTO TimestampItemEntity(time, version)
VALUES (?, 1);

updateTimestamp:
UPDATE TimestampItemEntity SET time = ?, version = version + 1 WHERE id = ?;
</code></pre><p>Every item has:</p><ul><li>An auto-generated ID </li><li>The timestamp which will be shown in the UI</li><li>A version which is increased when the item is updated</li></ul><h2 id="integration-tests">Integration tests</h2><p>Before diving into the tests, I just want to point out that all tests use Koin for replacing the production dependencies and retrieving classes used in the test. </p><p>In this article I won&apos;t get into the details of using Koin in tests, if &#xA0;you want to learn more about this topic I invite you to <a href="https://akjaw.com/using-ktor-client-mock-engine-for-integration-and-ui-tests/">my other article focused around this topic</a>. All the test code is also available in the <a href="https://github.com/AKJAW/sqldelight-testing/tree/main/shared/src/commonTest/kotlin/com/akjaw/sqldelight/testing/domain?ref=akjaw.com">GitHub Repository</a> if you want to just look at the source code.</p><h3 id="testing-added-items">Testing added items</h3><pre><code class="language-kotlin">class AddTimestampItemTest : KoinComponent {

    private val tableQueries: TableQueries by inject()
    private val mockTimestampProvider: MockTimestampProvider by inject()
    private val systemUnderTest: AddTimestampItem by inject()

    @BeforeTest
    fun setUp() {
        startTestKoin()
    }

    @AfterTest
    fun tearDown() {
        stopTestKoin()
    }

    @Test
    fun `Added item uses the current timestamp as the name`() = runTest {
        mockTimestampProvider.setNextTimestamp(123)

        systemUnderTest.execute()

        val result = tableQueries.selectAll().executeAsList().first()
        result.time shouldBe 123
    }

    @Test
    fun `Added item has a version equal to 1`() = runTest {
        systemUnderTest.execute()

        val result = tableQueries.selectAll().executeAsList().first()
        result.version shouldBe 1
    }
}
</code></pre><p>The test cases set a predefined value for the <strong>TimestampProvider</strong>, then invoke the UseCase and verifies that the database is in the correct state.</p><h3 id="timestampprovider-test-double">TimestampProvider test double</h3><pre><code class="language-kotlin">interface TimestampProvider {

    fun getTimestampMilliseconds(): Long
}

class MockTimestampProvider : TimestampProvider {
    private var timestampValues: MutableList&lt;Long&gt; = mutableListOf()
    private var lastValue: Long = 0

    fun setNextTimestamp(value: Long) {
        timestampValues.add(value)
    }

    override fun getTimestampMilliseconds(): Long {
        lastValue = timestampValues.firstOrNull() ?: lastValue
        timestampValues.removeFirstOrNull()
        return lastValue
    }
}</code></pre><p><strong>MockTimestampProvider</strong> allows setting predefined values that are returned when the &#xA0;<strong>getTimestampMilliseconds()</strong> is called. The reason for using a &quot;Queue&quot; for the values will be explained later, but it has to do iOS UI testing.</p><h3 id="setting-up-the-in-memory-database-driver">Setting up the in-memory database driver</h3><p>The in-memory driver creation is done by an expect-actual function:</p><pre><code class="language-kotlin">// commonMain
expect fun createInMemorySqlDriver(): SqlDriver</code></pre><p></p><p>The Android in-memory function is pretty simple because it just uses the JVM JDBC driver:</p><pre><code class="language-kotlin">// androidMain
actual fun createInMemorySqlDriver(): SqlDriver {
    val driver: SqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
    AppDatabase.Schema.create(driver)
    return driver
}
</code></pre><p></p><p>For iOS, it gets more complicated:</p><pre><code class="language-kotlin">// iosMain
private var index = 0

actual fun createInMemorySqlDriver(): SqlDriver {
    index++
    val schema = AppDatabase.Schema
    return NativeSqliteDriver(
        DatabaseConfiguration(
            name = &quot;test-$index.db&quot;,
            version = schema.version,
            create = { connection -&gt;
                wrapConnection(connection) { schema.create(it) }
            },
            upgrade = { connection, oldVersion, newVersion -&gt;
                wrapConnection(connection) { 
                    schema.migrate(it, oldVersion, newVersion) 
                }
            },
            inMemory = true
        )
    )
}</code></pre><p>The base function was created by Touchlab in their <a href="https://github.com/touchlab/KaMPKit/blob/1d54b14c66dd55d865ea29d3f0051f5c793c4010/shared/src/iosTest/kotlin/co/touchlab/kampkit/TestUtilIOS.kt?ref=akjaw.com">KaMPKit starter</a>. I just added the index variable, so the database name is different on each invocation. Without this, the same database is reused in each test case, making the tests unpredictable (running order changes the test outcome).</p><p>The <strong>createInMemorySqlDriver()</strong> function is then used by Koin to introduce the correct database driver to the dependency graph:</p><pre><code class="language-kotlin">val testModule = module {
    single { MockTimestampProvider() }
    single&lt;TimestampProvider&gt; { get&lt;MockTimestampProvider&gt;() }
    single&lt;SqlDriver&gt; { createInMemorySqlDriver() }
}</code></pre><p></p><h3 id="testing-items-retrieval">Testing items retrieval</h3><pre><code class="language-kotlin">class GetTimestampItemsTest : KoinComponent {

    private val tableQueries: TableQueries by inject()
    private val systemUnderTest: GetTimestampItems by inject()

    @BeforeTest
    fun setUp() {
        startTestKoin()
    }

    @AfterTest
    fun tearDown() {
        stopTestKoin()
    }

    @Test
    fun `The date is correctly mapped`() = runTest {
        tableQueries.insertItem(1652980264000)
        tableQueries.insertItem(1652980224000)

        val result = systemUnderTest.execute().first()

        assertSoftly {
            result shouldHaveSize 2
            result[0].name shouldBe &quot;2022-05-19T17:11:04&quot;
            result[1].name shouldBe &quot;2022-05-19T17:10:24&quot;
        }
    }
}</code></pre><p>In the set-up phase, two items are inserted to the database, then the UseCase retrieves the items and the test case verifies that the timestamp is correctly formatted.</p><h2 id="android-ui-tests">Android UI Tests</h2><h3 id="setting-up-the-in-memory-database-driver-1">Setting up the in-memory database driver</h3><p>The significant difference with the in-memory database driver for Android UI tests is that the Android driver has to be used:</p><pre><code class="language-kotlin">AndroidSqliteDriver(
    schema = AppDatabase.Schema,
    context = get(),
    name = null,
)
</code></pre><p>The Android OS does not support the JVM database driver, so the normal AndroidSqliteDriver is required, however passing null as the name of the database, makes the database an in-memory one:</p><pre><code class="language-Kotlin">/**
* @param name Name of the database file, or null for an in-memory database.
* @return This
*/
@NonNull
public Builder name(@Nullable String name) {
    mName = name;
    return this;
}</code></pre><p></p><h3 id="the-test">The test</h3><p>The UI test cases focus on adding a new item and updating an existing one. As I stated previously, Koin usage for testing is covered <a href="https://akjaw.com/using-ktor-client-mock-engine-for-integration-and-ui-tests/">in my other article</a>.</p><pre><code class="language-kotlin">class MatchScreenTest : KoinComponent {

    @get:Rule
    val composeTestRule = createAndroidComposeRule&lt;MainActivity&gt;()
    val mockTimestampProvider: MockTimestampProvider by inject()

    @Before
    fun setUp() {
        ...
    }

    @Test
    fun addingAnItemPopulatesTheListWithCurrentTimestamp() {
        mockTimestampProvider.setNextTimestamp(1653823433000)

        composeTestRule.onNodeWithText(&quot;Add&quot;).performClick()

        composeTestRule.waitUntilCatching(1000) {
            composeTestRule.onNodeWithText(&quot;2022-05-29T11:23:53&quot;).assertIsDisplayed()
        }
    }

    @Test
    fun refreshingAnItemUpdatesTheNameWithCurrentTimestamp() {
        mockTimestampProvider.setNextTimestamp(0)
        composeTestRule.onNodeWithText(&quot;Add&quot;).performClick()
        mockTimestampProvider.setNextTimestamp(1653823433000)

        composeTestRule.onNodeWithContentDescription(&quot;Refresh&quot;).performClick()

        composeTestRule.waitUntilCatching(1000) {
            composeTestRule.onNodeWithText(&quot;2022-05-29T11:23:53&quot;).assertIsDisplayed()
        }
    }

    private fun ComposeTestRule.waitUntilCatching(
        timeoutMillis: Long, assertion: () -&gt; Unit
    ) = ...
}</code></pre><p>The test cases set a pre-defined value for the <strong>TimestampProvider</strong> just as the integration test did, then it executes an action and verifies if the correct UI text is displayed.</p><h2 id="ios-ui-tests">iOS UI Tests</h2><h3 id="setting-up-the-in-memory-database-driver-2">Setting up the in-memory database driver</h3><p>The same <strong>createInMemorySqlDriver()</strong> can be used just like in the integration tests.</p><h3 id="setting-up-dependency-injection">Setting up dependency injection</h3><p>Using Koin in iOS UI testing is not as straight forward as for Android, so I&apos;ll quickly run through the steps.</p><pre><code class="language-kotlin">fun setUpKoinForTesting() {
    val modules = listOf(
        *sharedModules,
        module {
            single&lt;SqlDriver&gt; { createInMemorySqlDriver() }
            single { MockTimestampProvider() }
            single&lt;TimestampProvider&gt; { get&lt;MockTimestampProvider&gt;() }
        }
    )
    unloadKoinModules(modules)
    loadKoinModules(modules)
}</code></pre><p>The <strong>setUpKoinForTesting()</strong> function needs to be called between every iOS test case to &quot;reload&quot; the database.</p><pre><code class="language-kotlin">object TestDoubleRetriever : KoinComponent {

    val mockTimestampProvider: MockTimestampProvider
        get() = get()
}</code></pre><p><strong>TestDoubleRetriever</strong> is just a helper object to make test double retrieval easier on iOS.</p><h3 id="the-test-1">The test</h3><p>The main difference between Android and iOS UI testing is that on iOS, <a href="https://stackoverflow.com/questions/38084480/how-to-access-the-app-delegate-from-a-ui-test?ref=akjaw.com">we are not allowed to touch production code inside the test</a>. So, calling <strong>setUpKoinForTesting()</strong> in the setUp function will not work.</p><p>The iOS testing framework allows sending so-called launch arguments:</p><pre><code class="language-kotlin">app.launchArguments = [&quot;enable-testing&quot;, &quot;-mock-time&quot;, &quot;0,1653823433000&quot;]
app.launch()</code></pre><p>These values can be used in the production code to set up the test doubles to the correct state:</p><pre><code class="language-swift">#if DEBUG
if CommandLine.arguments.contains(&quot;enable-testing&quot;) {
    SetUpKoinForTestingKt.setUpKoinForTesting()
    UserDefaults.standard.string(forKey: &quot;mock-time&quot;)?
        .split(separator: &quot;,&quot;)
        .map { timeString in
            Int64(timeString)
        }
        .compactMap { $0 }
        .forEach { time in
            TestDoubleRetriever.shared.mockTimestampProvider
                .setNextTimestamp(value: time)
        }
}
#endif</code></pre><p></p><p>The test cases are pretty much the same as for the Android app, just with a different test double set up:</p><pre><code class="language-swift">class KaMPKitiOSUITests: XCTestCase {
    let app = XCUIApplication()

    func testAddingAnItemPopulatesTheListWithCurrentTimestamp() {
        app.launchArguments = [&quot;enable-testing&quot;, &quot;-mock-time&quot;, &quot;1653823433000&quot;]
        app.launch()
        
        app.staticTexts[&quot;Add&quot;].tap()

        let itemExists = app.staticTexts[&quot;2022-05-29T11:23:53&quot;].exists
        XCTAssert(itemExists)
    }
    
    func testRefreshingAnItemUpdatesTheNameWithCurrentTimestamp() {
        app.launchArguments = [&quot;enable-testing&quot;, &quot;-mock-time&quot;, &quot;0,1653823433000&quot;]
        app.launch()
        app.staticTexts[&quot;Add&quot;].tap()
        
        app.buttons[&quot;Refresh&quot;].tap()

        let itemExists = app.staticTexts[&quot;2022-05-29T11:23:53&quot;].exists
        XCTAssert(itemExists)
    }
}</code></pre><p>As you can see, the <strong>TimestampProvider</strong> values have to be defined before the app even launches. This limitation enforces that the Kotlin test doubles have to provide a way of defining multiple return values beforehand, and that is the reason for the &quot;Queue&quot; in the <strong>MockTimestampProvider</strong>.</p><h2 id="summary">Summary</h2><p>I hope that this simple example shows how SQLDelight in-memory drivers can be helpful when testing the application.</p><p>Besides SQLDelight I&apos;ve also covered <a href="https://akjaw.com/using-ktor-client-mock-engine-for-integration-and-ui-tests/">Mocking Ktor network requests</a> and <a href="https://akjaw.com/using-apollo-kotlin-data-builders-for-testing/">Apollo GraphQL fake networking</a> which might interest you. Additionally, I have an article series about <a href="https://akjaw.com/5-beginner-testing-mistakes-i-noticed-while-working-with-less-experienced-developers/">what testing mistakes you should avoid</a> if you want to improve your test suite.</p><h3 id="the-ui-tests-in-action">The UI tests in action</h3><figure class="kg-card kg-embed-card"><iframe src="https://player.vimeo.com/video/719180547?h=b50125653c&amp;app_id=122963" width="196" height="426" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen title="Android UI tests"></iframe></figure><figure class="kg-card kg-embed-card"><iframe src="https://player.vimeo.com/video/719180897?h=54631a12f9&amp;app_id=122963" width="296" height="640" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen title="iOS UI tests"></iframe></figure>]]></content:encoded></item><item><title><![CDATA[Refactoring an Android App to Kotlin Multiplatform]]></title><description><![CDATA[<p>Recently I reactivated my old project <a href="https://github.com/AKJAW/Timi-Multiplatform?ref=akjaw.com">Timi</a> which I used to learn Compose. This time my focus is on learning the iOS side of Kotlin Multiplatform, I&apos;m hoping that this experience will help me better understand my colleagues on the other platform.</p><p>Working on a smaller project also</p>]]></description><link>https://akjaw.com/refactoring-android-to-kotlin-multiplatform/</link><guid isPermaLink="false">6238262b03d3b43079b916c0</guid><category><![CDATA[Multiplatform]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Android]]></category><category><![CDATA[iOS]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Thu, 14 Apr 2022 17:16:27 GMT</pubDate><media:content url="https://akjaw.com/content/images/2022/04/hero-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2022/04/hero-1.png" alt="Refactoring an Android App to Kotlin Multiplatform"><p>Recently I reactivated my old project <a href="https://github.com/AKJAW/Timi-Multiplatform?ref=akjaw.com">Timi</a> which I used to learn Compose. This time my focus is on learning the iOS side of Kotlin Multiplatform, I&apos;m hoping that this experience will help me better understand my colleagues on the other platform.</p><p>Working on a smaller project also allows for more experiments, in this project I&apos;ll try to share as much code as possible, and see how that integrates on both mobile platforms.</p><p>In this article, I&apos;ll share my experience making a Kotlin Multiplatform app from an Android only codebase. If you&apos;re interested in the commits of this process, they&apos;re available in the <a href="https://github.com/AKJAW/Timi-Multiplatform/commits/kmp-refactor?ref=akjaw.com">GitHub repository</a> (starting from the 2022 commits).</p><p>The modularization used for this project is explained more in-depth in my <a href="https://akjaw.com/modularizing-a-kotlin-multiplatform-mobile-project/">Modularizing a Kotlin Multiplatform Mobile Project</a>.</p><h2 id="adding-kotlin-multiplatform-and-ios-to-the-codebase">Adding Kotlin Multiplatform and iOS to the codebase</h2><ol><li>My first step was creating a new playground project based on the <a href="https://github.com/touchlab/KaMPKit?ref=akjaw.com"><a href="https://github.com/touchlab/KaMPKit?ref=akjaw.com">KaMPKit</a> starter</a>. I removed everything I didn&apos;t need and added a simple class resembling one feature of Timi (A stopwatch). I used this class on both Android and iOS to see if everything works.</li><li>In the Timi codebase, I did some gradle related clean up to make Kotlin Multiplatform integration easier (replaced Groovy with <a href="https://docs.gradle.org/current/userguide/kotlin_dsl.html?ref=akjaw.com">Kotlin DSL</a> and added <a href="https://jmfayard.github.io/refreshVersions/?ref=akjaw.com">refreshVersions</a> for dependency version management)</li><li>The next step was copying over the playground KMP module and the iOS project into the Timi codebase.</li><li>The first KMP integration was done o Android, and then I got it working on iOS.</li></ol><p>After these steps, I had a working bare-bones Kotlin Multiplatform app which shares some code with both platforms.</p><h2 id="moving-existing-logic-to-kotlin-multiplatform">Moving existing logic to Kotlin Multiplatform</h2><p>Keep in mind that the project is pretty small, which allowed me to do the refactor in much bigger steps without fear of breaking something.</p><p>The Android codebase was modularized, which allowed me to move the logic to KMP on a module by module basis.</p><h3 id="the-core-module">The core module</h3><p>At the start, I focused on the &quot;core&quot; module of the application. This module contains interfaces and data structures which are used in many of the other modules. Fortunately, the module didn&apos;t contain any Java specific libraries, so it was easy to move it to KMP.</p><p>The problem came with Dependency Injection, the Android app used Dagger Hilt for DI, but for Kotlin Multiplatform I wanted to use Koin. In order to make the process straightforward without a lot of changes, I marked the Hilt module as a KoinComponent and used Koin to retrieve the dependencies:</p><pre><code class="language-kotlin">@Module
@InstallIn(SingletonComponent::class)
object TimestampModule: KoinComponent {

    @Provides
    internal fun provideTimestampProvider(): TimestampProvider = get()
}</code></pre><p>This allowed me to use Hilt in the Android app, but still retrieve the KMP dependencies from Koin.</p><p>After the core module I focused on moving the feature modules which contained all the layers (Presentation, Domain, Data and UI which I didn&apos;t move).</p><h3 id="domain-and-data-layer">Domain and Data layer</h3><p>At first, I stared with the Domain and Data layer of the stopwatch module. This module seemed like a good start because it had the least amount of external dependencies.</p><p>Moving the stopwatch classes was pretty easy just like the core module, however there were places where Java libraries were used, so I had to replace them with a Kotlin alternative, or just &quot;improvise&quot; and leave a TODO comment for later &#x1F64A;</p><p>I moved the Unit tests last because I wanted to ensure that the production code was still working after moving. After everything was green (The tests passed) I had to also move them to KMP. This task was a lot more involved because I wrote these tests using JUnit5 which is not available in Common Kotlin Multiplatform. To move the tests I had to refactor them to use plain framework features (<a href="https://akjaw.com/kotlin-multiplatform-parameterized-tests-and-grouping/">no nested classes, no parameterized tests</a>) and then move them to KMP. </p><p>More in-depth explanation about the Kotlin Multiplatform testing can be found here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/testing-on-kotlin-multiplatform-and-strategy-to-speed-up-development/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Testing on Kotlin Multiplatform Mobile and a Strategy to Speed Up Development Time</div><div class="kg-bookmark-description">Automated tests are an integral part of developing software, they help catch bugs before they reach the users and save developers time by cutting down manual testing.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Refactoring an Android App to Kotlin Multiplatform"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2022/01/kotlin-multiplatform-testing.png" alt="Refactoring an Android App to Kotlin Multiplatform"></div></a></figure><h3 id="presentation-layer">Presentation layer</h3><p>After checking that the Android app stopwatch still works, I focused on the Presentation layer of the stopwatch module. This included moving the ViewModel to the shared code (I still haven&apos;t come up with a mechanism for saving the data on process death). </p><p>After moving the presentation layer successfully, &#xA0;I created some basic UI for the iOS app. Once both the apps had the stopwatch feature working, I moved on to the next feature module. Every module was moved repeating these steps: moving Data and Domain, then Presentation, followed by iOS UI.</p><!--kg-card-begin: html--><div class="ml-embedded" data-form="Tt6wpS"></div><!--kg-card-end: html--><h2 id="problems">Problems</h2><p>While moving logic into KMP, I&apos;ve encountered some problems which I either fixed, or left for my future self to fix.</p><h3 id="java-dependencies">Java dependencies</h3><p>When it comes to the Java standard library, Kotlin should have corresponding functionalities, although there are some exceptions (mostly related to concurrency). The official <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/max-by.html?ref=akjaw.com">Kotlin documentation</a> provides great information about if a given functionality is available on Kotlin/JVM as well as Kotlin Native.</p><p>Libraries might be harder to migrate, even though the Kotlin Multiplatform ecosystem is growing every day, it is still relatively young. Because of this, not every library supports KMP or has an alternative to it. Fortunately all the basic blocks of mobile development exist: Networking (<a href="https://ktor.io/docs/http-client-engines.html?ref=akjaw.com#configure">Ktor</a> or <a href="https://www.apollographql.com/docs/kotlin/tutorial/00-introduction/?ref=akjaw.com">Apollo</a>), Database (<a href="https://cashapp.github.io/sqldelight/multiplatform_sqlite/?ref=akjaw.com">SQLDelight</a>), Key Value Store (<a href="https://github.com/russhwolf/multiplatform-settings?ref=akjaw.com">Settings</a>), Dependency Injection (<a href="https://insert-koin.io/?ref=akjaw.com">Koin</a>) the list could go on and on. To help with this, there exists a <a href="https://github.com/AAkira/Kotlin-Multiplatform-Libraries?ref=akjaw.com">curated list of Multiplatform libraries</a>.</p><h3 id="compose-and-swift-ui-color">Compose and Swift UI color</h3><p>Timi heavily focuses on colors, every task is assigned a color. This color will be then used in other screens of the app. Unfortunately, the Android app did not use a framework-agnostic color abstraction and just used the Compose Color class everywhere. As you can guess, this was problematic when moving classes from Android to KMP.</p><p>The solution that I came up with is this:</p><pre><code class="language-kotlin">data class TaskColor(
    val red: Float,
    val green: Float,
    val blue: Float,
)</code></pre><p>Which can be used on both the Compose and SwiftUI framework.</p><h3 id="the-database">The database</h3><p>Fortunately, the Android app used SQLDelight for the database, which has support for Kotlin Multiplatform. The transition to KMP only required changing the database path, package and the database driver. I didn&apos;t create any migration files because the database is still in its early phases and will probable change in the near future. Although, I&apos;m pretty sure it should work with no problems as long as the database name and structure isn&apos;t altered.</p><p>The database also had integration tests which used the in-memory database driver, unfortunately this driver only exists on the JVM. For Kotlin / Native, there is an in-memory database option, but it requires some tweaking in order to work (Although this is a topic for a separate article &#x1F604;).</p><h2 id="whats-next">What&apos;s next</h2><p>I&apos;ll try to move as much as I can into the shared codebase, and create a corresponding iOS screen for that feature. When it comes to the ViewModel, I&apos;d like to come up with a way to survive process death on Android. In the meantime, I&apos;ll try to understand the iOS platform better and find out the best ways to share / consume the shared code on iOS.</p><p>Thank you for reading this, I hope my journey might be helpful for some of you wanting to try out Kotlin Multiplatform on a more &quot;brown field&quot; project. My migration from Android to Kotlin Multiplatform isn&apos;t as clean as I wanted it to be. But this mostly has to do with the fact that I wanted to get my hands dirty with SwfitUi as soon as possible.</p>]]></content:encoded></item><item><title><![CDATA[Writing UI Tests for Pager Layouts in Jetpack Compose]]></title><description><![CDATA[Writing UI tests for pager layouts can be problematic in Compose. In this post, I'll share my experience and solutions when dealing with such screens.]]></description><link>https://akjaw.com/wiriting-ui-tests-for-pager-layouts-jetpack-compose/</link><guid isPermaLink="false">61ee6a75fba33c6d2cd0fa7f</guid><category><![CDATA[Testing]]></category><category><![CDATA[Android]]></category><category><![CDATA[Compose]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Aleksander Jaworski]]></dc:creator><pubDate>Sat, 19 Feb 2022 15:18:58 GMT</pubDate><media:content url="https://akjaw.com/content/images/2022/02/testing-compose-pager.png" medium="image"/><content:encoded><![CDATA[<img src="https://akjaw.com/content/images/2022/02/testing-compose-pager.png" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"><p>Recently, I was tasked with creating a Pager screen using Compose. Because the screen had a lot of edge cases in it, I also wanted to write UI tests alongside the screen to ensure all features are covered. In this post, I&apos;ll share my experience on testing such screens.</p><p>All the working code samples can be found here on the GitHub repository <a href="https://github.com/AKJAW/testing-compose-pager?ref=akjaw.com">testing-compose-pager</a>.</p><p>Here&apos;s a brief overview on what will be touched in this article:</p><ul><li>The semantic node tree for pager layouts</li><li>Swiping between screens</li><li>Selecting nodes only from a specific pager layout page</li><li>Selecting off-screen tabs </li></ul><h2 id="the-app-under-test">The app under test </h2><p>The main screen just allows navigating to the two pager screens (It will not be tested)</p><figure class="kg-card kg-image-card"><img src="https://akjaw.com/content/images/2022/02/device-2022-02-08-175856-2-1.png" class="kg-image" alt="Writing UI Tests for Pager Layouts in Jetpack Compose" loading="lazy" width="300" height="634"></figure><p>The <strong>static</strong> pager screen has 3 predefined pages, the summary page has two buttons that allow changing the selected page. The other two pages just contain a simple text.</p><p>The <strong>dynamic</strong> pager screen allows for increasing and decreasing the pager layout page count. Every page has more or less the same content.</p><p>The Pager library used for this article comes from the <a href="https://google.github.io/accompanist/pager/?ref=akjaw.com">Accompanist Utils library for Jetpack Compose</a>.</p><h2 id="the-static-pager-screen">The Static Pager Screen</h2><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2022/02/device-2022-02-08-175943-2.png" width="300" height="633" loading="lazy" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"></div><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2022/02/device-2022-02-08-175955-2.png" width="300" height="633" loading="lazy" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"></div></div></div><figcaption>The summary allows navigating to other tabs, Info and Details just show some text</figcaption></figure><p>Here&apos;s a simplified structure of the static pager screen composable, the full source code is available on the <a href="https://github.com/AKJAW/testing-compose-pager/blob/main/app/src/main/java/com/akjaw/testing/compose/pager/StaticPagerScreen.kt?ref=akjaw.com">GitHub repository</a>.</p><pre><code class="language-kotlin">enum class StaticPagerScreenPage {
    Summary,
    Info,
    Details,
}</code></pre><pre><code class="language-kotlin">@OptIn(ExperimentalPagerApi::class)
@Composable
fun StaticPagerScreen() {
    val pages = remember {
        listOf(
            StaticPagerScreenPage.Summary,
            StaticPagerScreenPage.Info,
            StaticPagerScreenPage.Details,
        )
    }
    Column {
        TabRow() {
            ...
        }
        HorizontalPager() { index -&gt;
            val page = pages[index]
            Box() {
                ....
            }
        }
    }
}</code></pre><h2 id="the-static-pager-tests">The Static Pager Tests</h2><p>The static pager screen is easier to test because every page has unique content. Meaning that the testing framework won&apos;t be confused when selecting nodes (The next and previous pages don&apos;t interfere).</p><h3 id="set-up">Set up</h3><pre><code class="language-kotlin">@Before
fun setUp() {
    composeTestRule.setContent {
        TestingComposePagerTheme {
            StaticPagerScreen()
        }
    }
}</code></pre><h3 id="pager-semantic-node-tree">Pager semantic node tree</h3><p>Before showing the tests, I want to explain how compose interprets the pager content in its semantic node tree. </p><p>By default, the pager works by &quot;placing&quot; the current, previous and next page in the tree. This means that at the same time, three pages can exist in the semantic node tree. Here&apos;s a simplified tree when opening the static pager screen:</p><pre><code class="language-text">|-Node #5 at (l=0.0, t=303.0, r=1080.0, b=2148.0)px
   CollectionInfo = &apos;androidx.compose.ui.semantics.CollectionInfo@bf668a0&apos;
   Actions = [IndexForKey, ScrollBy, ScrollToIndex]
    |-Node #18 at (l=0.0, t=303.0, r=1080.0, b=2148.0)px
    |  |-Node #19 at (l=241.0, t=675.0, r=840.0, b=768.0)px
    |  | Text = &apos;[The Summary page]&apos;
    |  | Actions = [GetTextLayoutResult]
    |-Node #26 at (l=0.0, t=303.0, r=1080.0, b=2148.0)px
       |-Node #27 at (l=0.0, t=303.0, r=662.0, b=396.0)px
         Text = &apos;[The Information page]&apos;
         Actions = [GetTextLayoutResult]</code></pre><p>The Node #18 and #26 represent the content of the first and second page. In tests, both of these pages are accessible, but only the first one is displayed:</p><pre><code class="language-kotlin">@Test
fun theSecondPageIsInTheNodeTree() {
    composeTestRule.onNodeWithText(&quot;The Summary page&quot;).assertIsDisplayed()
    composeTestRule.onNodeWithText(&quot;The Information page&quot;).assertIsNotDisplayed()
}</code></pre><p></p><p>The third page however does not exist in the node tree:</p><pre><code class="language-kotlin">@Test
fun theThirdPageDoesNotExistInTheNodeTree() {
    composeTestRule.onNodeWithText(&quot;The Details page&quot;).assertDoesNotExist()
}</code></pre><p></p><p>This behavior of rendering sibling pages can be problematic when dealing with pages that have content which is not unique between pages. The dynamic pager screen section goes into details on how to differentiate between these types of pages in tests.</p><h3 id="swiping-between-pages">Swiping between pages</h3><p>Swiping horizontally between content is one of the most recognizable features of pagers. Fortunately, with compose, swiping gestures are really easy to perform:</p><pre><code class="language-kotlin">@Test
fun swipingLeftOnRootTwoTimesOpensTheDetailsPage() {
    composeTestRule.onRoot().performTouchInput {
        swipeLeft()
        swipeLeft()
    }

    composeTestRule.onNodeWithText(&quot;Details&quot;).assertIsSelected()
    composeTestRule.onNodeWithText(&quot;The Details page&quot;).assertIsDisplayed()
}</code></pre><p>The test verification ensures that the <strong>Details</strong> tab is selected, and that the details page content is displayed after swiping two times to the left.</p><p>Performing swiping <em>onRoot</em> works because swipes are performed in the middle of the node (which, in this case, the whole screen):</p><pre><code class="language-kotlin">val height: Int get() = visibleSize.height

val centerY: Float get() = height / 2f

fun TouchInjectionScope.swipeLeft(
    startX: Float = right,
    endX: Float = left,
    durationMillis: Long = 200
) {
    ...
    val start = Offset(startX, centerY)
    val end = Offset(endX, centerY)
    swipe(start, end, durationMillis)
}</code></pre><h3 id="changing-pages-by-tapping-the-tab">Changing pages by tapping the tab</h3><p>Another popular way of changing pages is by tapping their corresponding tab.</p><pre><code class="language-kotlin">@Test
fun clickingInfoTabOpensTheInfoPage() {
    composeTestRule.onNodeWithText(&quot;Info&quot;).performClick()

    composeTestRule.onNodeWithText(&quot;Info&quot;).assertIsSelected()
    composeTestRule.onNodeWithText(&quot;The Information page&quot;).assertIsDisplayed()
}</code></pre><h3 id="changing-pages-by-tapping-the-summary-page-button">Changing pages by tapping the summary page button</h3><p>There might be also additional ways of changing pages.</p><pre><code class="language-kotlin">@Test
fun clickingGoToInfoOpensTheInfoPage() {
    composeTestRule.onNodeWithText(&quot;Go to info&quot;).performClick()

    composeTestRule.onNodeWithText(&quot;Info&quot;).assertIsSelected()
    composeTestRule.onNodeWithText(&quot;The Information page&quot;).assertIsDisplayed()
}</code></pre><h3 id="static-pager-summary">Static pager summary</h3><p>Because all static pages have unique content, all nodes be accessed like in normal composable screens i.e. using <em>onNodeWithText</em>, <em>onNodeWithTag, onNodeWithContentDescription.</em></p><p>The full static test source code is available on <a href="https://github.com/AKJAW/testing-compose-pager/blob/main/app/src/androidTest/java/com/akjaw/testing/compose/pager/StaticPagerScreenTest.kt?ref=akjaw.com">GitHub</a> and here are the tests in action:</p><figure class="kg-card kg-embed-card"><iframe src="https://player.vimeo.com/video/682937687?h=71cd3d3333&amp;app_id=122963" width="202" height="426" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen title="Android UI Tests for the Static Pager Screen"></iframe></figure><h2 id="the-dynamic-pager-screen">The Dynamic Pager Screen</h2><p>This screen is more involved because the number of pages can change, and their content is not unique (The increase and decrease buttons).</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2022/02/device-2022-02-08-175840-2.png" width="300" height="634" loading="lazy" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"></div><div class="kg-gallery-image"><img src="https://akjaw.com/content/images/2022/02/device-2022-02-08-175919-2.png" width="300" height="634" loading="lazy" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"></div></div></div><figcaption>Every page can increase or decrease the page count</figcaption></figure><p>Here&apos;s a simplified structure of the dynamic pager screen composable, the full source code is available on the <a href="https://github.com/AKJAW/testing-compose-pager/blob/main/app/src/main/java/com/akjaw/testing/compose/pager/DynamicPagerScreen.kt?ref=akjaw.com">GitHub repository</a>.</p><pre><code class="language-kotlin">@OptIn(ExperimentalPagerApi::class)
@Composable
fun DynamicPagerScreen() {
    var pageCount by remember { mutableStateOf(1) }
    val pages = remember(pageCount) { List(pageCount) { &quot;Page $it&quot; } }
    Column {
        ScrollableTabRow() {
           ...
        }
        HorizontalPager(
            count = pages.size,
            state = pagerState,
        ) { index -&gt;
            val page = pages[index]
            Box() {
                ...
            }
        }
    }
}</code></pre><p></p><p>In order to make the tests easier to write, some test tags can be added (a content description can also be used instead):</p><pre><code class="language-kotlin">Column {
    ScrollableTabRow(modifier = Modifier.testTag(&quot;dynamic-pager-tab-row&quot;)) { ... }
    HorizontalPager() { index -&gt;
        val page = pages[index]
        Box(modifier = Modifier.testTag(&quot;dynamic-pager-$index&quot;)) { ... }
    }
}</code></pre><p></p><p>The tags can also be extracted to a companion object, to make maintenance easier:</p><pre><code class="language-kotlin">object TestTagsDynamicPagerScreen {

    private const val pager = &quot;dynamic-pager&quot;
    const val tabRow = &quot;dynamic-pager-tab-row&quot;

    fun getPageTag(index: Int) = &quot;$pager-$index&quot;
}

...

Column {
    ScrollableTabRow(modifier = Modifier.testTag(TestTagsDynamicPagerScreen.tabRow)) { ... }
    HorizontalPager() { index -&gt;
        val page = pages[index]
        Box(modifier = Modifier.testTag(TestTagsDynamicPagerScreen.getPageTag(index))) { ... }
    }
}
</code></pre><h2 id="the-dynamic-pager-tests">The Dynamic Pager Tests</h2><h3 id="set-up-1">Set up</h3><pre><code class="language-kotlin">@Before
fun setUp() {
    composeTestRule.setContent {
        TestingComposePagerTheme {
            DynamicPagerScreen()
        }
    }
}</code></pre><h3 id="dealing-with-repeating-page-content">Dealing with repeating page content</h3><p>Because every page has the same two buttons, the tests will need to specify which page needs to be used. </p><p>For example, on entering, the dynamic pager screen only has one page available. This is the reason is why the following snipped of code works:</p><pre><code class="language-kotlin">@Test
fun worksIfOnlyOnePageExists() {
    composeTestRule.onNodeWithText(&quot;Increase&quot;).performClick()
}</code></pre><p></p><p>But when there are two pages (both have the <strong>Increase</strong> button) the testing framework gets confused:</p><pre><code class="language-kotlin">@Test
fun crashesOnSecondClick() {
    composeTestRule.onNodeWithText(&quot;Increase&quot;).performClick()
    composeTestRule.onNodeWithText(&quot;Increase&quot;).performClick()
}</code></pre><pre><code class="language-Text">Reason: Expected exactly &apos;1&apos; node but found &apos;2&apos; nodes that satisfy: (Text + EditableText contains &apos;Increase&apos; (ignoreCase: false))</code></pre><p></p><p>To get around this, the selector needs to know on which screen should the button be clicked:</p><pre><code class="language-kotlin">@Test
fun doesNotCrash() {
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()
}

private fun ComposeContentTestRule.onPagerButton(
    pageIndex: Int,
    text: String
): SemanticsNodeInteraction {
    val isOnTheCorrectPage =
        hasAnyAncestor(hasTestTag(TestTagsDynamicPagerScreen.getPageTag(pageIndex)))
    return onAllNodesWithText(text)
        .filterToOne(isOnTheCorrectPage)
}</code></pre><p>The basic idea is, that all <strong>Increase</strong> buttons are selected (using <em>onAllNodesWithText) </em>and then they are filtered using a matcher. The matcher works as follows: &quot;give me a node that has an <em>ancestor</em> that has a <em>test tag</em> with the requested <em>page index</em>.&quot;</p><p>In the example above, the <strong>Increase</strong> button is clicked only on the first page (index == <code>0</code>).</p><h3 id="checking-if-tabs-are-created">Checking if tabs are created</h3><p>The main feature of the dynamic screen is that the number of tabs can be changed on demand.</p><pre><code class="language-kotlin">@Test
fun increaseClickedOnceTimesThenTabCountIncreases() {
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()

    composeTestRule.onNodeWithText(&quot;Page 0&quot;).assertIsDisplayed()
    composeTestRule.onNodeWithText(&quot;Page 1&quot;).assertIsDisplayed()
}</code></pre><h3 id="checking-off-screen-tabs">Checking off-screen tabs</h3><p>When there are too many tabs, they will be added off-screen. In order to access them, the tab row will need to be scrolled horizontally.</p><pre><code class="language-kotlin">@Test
fun increaseClickedMultipleTimesThenTabCountIncreases() {
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()

    composeTestRule.onNodeWithTag(TestTagsDynamicPagerScreen.tabRow)
        .performTouchInput {
            swipeLeft()
        }
    composeTestRule.onNodeWithText(&quot;Page 1&quot;).assertIsDisplayed()
    composeTestRule.onNodeWithText(&quot;Page 2&quot;).assertIsDisplayed()
    composeTestRule.onNodeWithText(&quot;Page 3&quot;).assertIsDisplayed()
}</code></pre><h3 id="clicking-increase-on-a-different-page">Clicking increase on a different page</h3><p>The increase and decrease buttons are available on every page, and they should behave in the same way regardless of the page.</p><pre><code class="language-kotlin">@Test
fun increaseOnSecondTabClickedThenTabCountIncreases() {
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()
    composeTestRule.onNodeWithText(&quot;Page 1&quot;).performClick()

    composeTestRule.onPagerButton(pageIndex = 1, text = &quot;Increase&quot;).performClick()

    composeTestRule.onNodeWithText(&quot;Page 0&quot;).assertIsDisplayed()
    composeTestRule.onNodeWithText(&quot;Page 1&quot;).assertIsDisplayed()
    composeTestRule.onNodeWithText(&quot;Page 2&quot;).assertIsDisplayed()
}</code></pre><h3 id="swiping-on-page-content">Swiping on page content</h3><p>Instead of performing the swiping <em>onRoot </em>it can also be performed on one particular page.</p><pre><code class="language-kotlin">@Test
fun swipingLeftOnPageOpensTheCorrectPage() {
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()
    composeTestRule.onPagerButton(pageIndex = 0, text = &quot;Increase&quot;).performClick()

    composeTestRule.swipeOnPagerScreen(pageIndex = 0)
    composeTestRule.swipeOnPagerScreen(pageIndex = 1)

    composeTestRule.onNodeWithText(&quot;Page 2&quot;).assertIsSelected()
    composeTestRule.onNodeWithText(&quot;On page: 2&quot;).assertIsDisplayed()
}

private fun ComposeContentTestRule.swipeOnPagerScreen(
    pageIndex: Int
) {
    onNodeWithTag(TestTagsDynamicPagerScreen.getPageTag(pageIndex))
        .performTouchInput {
            swipeLeft()
        }
}</code></pre><h3 id="dynamic-pager-summary">Dynamic pager summary</h3><p>Repeating content on pages can be problematic, but compose offers a some-what elegant way of selecting content only on one particular page.</p><p>The full dynamic test source code is available on <a href="https://github.com/AKJAW/testing-compose-pager/blob/main/app/src/androidTest/java/com/akjaw/testing/compose/pager/DynamicPagerScreenTest.kt?ref=akjaw.com">GitHub</a> and below you can see the tests in action:</p><figure class="kg-card kg-embed-card"><iframe src="https://player.vimeo.com/video/682937790?h=0a635be26d&amp;app_id=122963" width="202" height="426" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen title="Android UI Tests for the Dynamic Pager Screen"></iframe></figure><h2 id="testing-screens-with-network-or-database-data">Testing screens with network or database data</h2><p>This article dived deep into the Compose framework using simple examples, but what about real use cases where we need to fetch something from the API, or when we need some database rows?</p><p>These exact topics are covered in some of my other articles:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/using-ktor-client-mock-engine-for-integration-and-ui-tests/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Using Ktor Client MockEngine for Integration and UI Tests</div><div class="kg-bookmark-description">MockEngine replaces real network calls with mocked ones that use pre-defined data and status codes. The engine can be shared between Integration and UI tests.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2021/12/ktor-mock-tests-1.png" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/kotlin-multiplatform-testing-sqldelight-integration-ios-android/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Kotlin Multiplatform In-Memory SQLDelight Database for Integration and UI Testing on iOS and Android</div><div class="kg-bookmark-description">Databases are an integral part of mobile application development, so it&#x2019;s important that these features are properly tested.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2022/06/hero.png" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://akjaw.com/using-apollo-kotlin-data-builders-for-testing/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Using Apollo Kotlin Data Builders for Testing</div><div class="kg-bookmark-description">Data Builders provide an easy way for creating GraphQL response classes. In this post I&#x2019;ll show how they can be used to improve your tests.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://akjaw.com/content/images/size/w256h256/2023/03/favicon-transparent.png" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"><span class="kg-bookmark-author">AKJAW</span><span class="kg-bookmark-publisher">Aleksander Jaworski</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://akjaw.com/content/images/2022/11/hero.png" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"></div></a></figure><hr><p>Side-note: Normally when writing UI Tests it&apos;s good to use some form of pattern, like the Robot pattern:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://jakewharton.com/testing-robots/?ref=akjaw.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Testing Robots &#x2013;&#xA0;Jake Wharton</div><div class="kg-bookmark-description">Libraries like Espresso allow UI tests to have stable interactions with your app, but without discipline these tests can become hard to manage and require frequent updating. This talk will cover how the so-called robot pattern allows you to create stable, readable, and maintainable tests with the ai&#x2026;</div><div class="kg-bookmark-metadata"><span class="kg-bookmark-author">Jake Wharton</span></div></div><div class="kg-bookmark-thumbnail"><img src="http://img.youtube.com/vi/7Y3qIIEyP5c/maxresdefault.jpg" alt="Writing UI Tests for Pager Layouts in Jetpack Compose"></div></a></figure><p>If you have a different way of testing pager screens in compose, feel free to share them below in the comment section.</p>]]></content:encoded></item></channel></rss>