<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-qlzhkarsu" -->

---
title: 11 Approvals, Green Tests, and a Broken Prod: How One...
description: A postmortem on a production bug caused by using Kotlin's `apply` scope function instead of `let` inside a Compose UI test DSL. The `applyIf` helper silently...
canonical: https://daily.dev/posts/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-qlzhkarsu
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: 11 Approvals, Green Tests, and a Broken Prod: How One Kotlin Keyword Blinded Our UI Automation | daily.dev
og:description: A postmortem on a production bug caused by using Kotlin's `apply` scope function instead of `let` inside a Compose UI test DSL. The `applyIf` helper silently...
og:url: https://daily.dev/posts/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-qlzhkarsu
og:image: https://api.daily.dev/og/posts/qlZhKARsU.png
og:image:alt: 11 Approvals, Green Tests, and a Broken Prod: How One Kotlin Keyword Blinded Our UI Automation
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 11 Approvals, Green Tests, and a Broken Prod: How One Kotlin Keyword Blinded Our UI Automation

**[ProAndroidDev](https://daily.dev/sources/pand)** · 10 min read · 1 upvotes · 0 comments

## Summary

A postmortem on a production bug caused by using Kotlin's `apply` scope function instead of `let` inside a Compose UI test DSL. The `applyIf` helper silently discarded every text assertion because `apply` always returns the original receiver, not the transformed result. This made all tests pass while checking almost nothing — and the MR had 11 approvals. The post explains the exact mechanism (apply vs let semantics on immutable types), why 11 reviewers missed it, and four layers of prevention: negative unit tests on matchers, redesigning the API to make the bug uncompilable, a custom Detekt rule, and `@CheckResult` annotations. It also argues that live-backend E2E tests breed the conditional DSL complexity where this class of bug thrives, and that hermetic mocks would have surfaced the issue immediately.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://proandroiddev.com/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-34e2b1a3216c>

## Questions this post answers

### Why does using apply instead of let on a Compose SemanticsMatcher silently break my test assertions?

Apply always returns the original receiver and discards the lambda's return value, so combining an immutable SemanticsMatcher with `and(...)` inside an apply block computes a new matcher and then throws it away, returning the original unchanged matcher. Since SemanticsMatcher is immutable, only let (or letIf) captures the transformed result correctly; apply is only safe on mutable receivers like builders or MockWebServer.

_Developers debugging silent Kotlin scope-function bugs can find similar deep-dive breakdowns curated on daily.dev._

### How can I prevent developers from accidentally using apply on an immutable Kotlin type like a Compose matcher?

Redesign the conditional helper so it can only return a transformed value, such as a letIf-based combinator with a type signature that returns the new matcher directly, making the blind apply-based version impossible to compile. A cheaper interim option is wrapping the immutable-returning call in your own extension annotated with androidx.annotation.CheckResult, which makes Android Lint flag any call site where the result is dropped as a bare statement.

_Teams designing footgun-proof Kotlin APIs can track patterns like this via daily.dev._

### Should mobile UI tests verify live backend data or use mocks?

Mobile UI tests should be isolated and deterministic, verifying client rendering, state, navigation, and animation rather than backend payload correctness, which belongs to backend contract and integration tests. Binding matchers to live, unpredictable server data forces complex conditional DSL logic, which is exactly where a silent assertion bug can hide for weeks instead of failing immediately with a hermetic mock like MockWebServer.

_Engineers weighing live-backend E2E versus mocked mobile UI tests can compare approaches through daily.dev._

## Similar posts on daily.dev

- [Kotlin Weekly \#502](https://daily.dev/posts/kotlin-weekly-502-7o2gij1op) · Kotlin Weekly · 1 upvotes · 0 comments
- [5 Kotlin Features Every Android Developer Should Actually Be Using part 1](https://daily.dev/posts/5-kotlin-features-every-android-developer-should-actually-be-using-part-1-puwyfdeef) · Medium · 3 upvotes · 0 comments
- [One POM Line, 42 Phantom Findings: How a Vendor SDK Broke Our detekt Build](https://daily.dev/posts/one-pom-line-42-phantom-findings-how-a-vendor-sdk-broke-our-detekt-build-maqiyl63r) · ProAndroidDev · 2 upvotes · 1 comments
- [IntelliJ IDEA's New Kotlin Coroutine Inspections, Explained](https://daily.dev/posts/intellij-idea-s-new-kotlin-coroutine-inspections-explained-bhub8oknf) · JetBrains · 0 upvotes · 0 comments

---

Tags: [#testing](https://daily.dev/tags/testing), [#kotlin](https://daily.dev/tags/kotlin), [#jetpack-compose](https://daily.dev/tags/jetpack-compose)

[View this post on daily.dev](https://daily.dev/posts/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-qlzhkarsu)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"11 Approvals, Green Tests, and a Broken Prod: How One Kotlin Keyword Blinded Our UI Automation","url":"https://daily.dev/posts/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-qlzhkarsu","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-qlzhkarsu"},"datePublished":"2026-07-15T04:21:13.853Z","dateModified":"2026-09-13T18:26:39.506Z","description":"A postmortem on a production bug caused by using Kotlin's `apply` scope function instead of `let` inside a Compose UI test DSL. The `applyIf` helper silently...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/62f8f374061d4cb9a92b9639d4b2bc96?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/62f8f374061d4cb9a92b9639d4b2bc96?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"ProAndroidDev","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"ProAndroidDev","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/656c1eb1f1ef4f91936e86fc16545114","url":"https://daily.dev/sources/pand"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-qlzhkarsu","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"testing,kotlin,jetpack-compose","timeRequired":"PT10M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"ProAndroidDev","item":"https://daily.dev/sources/pand"},{"@type":"ListItem","position":3,"name":"11 Approvals, Green Tests, and a Broken Prod: How One Kotlin Keyword Blinded Our UI Automation"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/11-approvals-green-tests-and-a-broken-prod-how-one-kotlin-keyword-blinded-our-ui-automation-qlzhkarsu#faq","mainEntity":[{"@type":"Question","name":"Why does using apply instead of let on a Compose SemanticsMatcher silently break my test assertions?","acceptedAnswer":{"@type":"Answer","text":"Apply always returns the original receiver and discards the lambda's return value, so combining an immutable SemanticsMatcher with `and(...)` inside an apply block computes a new matcher and then throws it away, returning the original unchanged matcher. Since SemanticsMatcher is immutable, only let (or letIf) captures the transformed result correctly; apply is only safe on mutable receivers like builders or MockWebServer. Developers debugging silent Kotlin scope-function bugs can find similar deep-dive breakdowns curated on daily.dev."}},{"@type":"Question","name":"How can I prevent developers from accidentally using apply on an immutable Kotlin type like a Compose matcher?","acceptedAnswer":{"@type":"Answer","text":"Redesign the conditional helper so it can only return a transformed value, such as a letIf-based combinator with a type signature that returns the new matcher directly, making the blind apply-based version impossible to compile. A cheaper interim option is wrapping the immutable-returning call in your own extension annotated with androidx.annotation.CheckResult, which makes Android Lint flag any call site where the result is dropped as a bare statement. Teams designing footgun-proof Kotlin APIs can track patterns like this via daily.dev."}},{"@type":"Question","name":"Should mobile UI tests verify live backend data or use mocks?","acceptedAnswer":{"@type":"Answer","text":"Mobile UI tests should be isolated and deterministic, verifying client rendering, state, navigation, and animation rather than backend payload correctness, which belongs to backend contract and integration tests. Binding matchers to live, unpredictable server data forces complex conditional DSL logic, which is exactly where a silent assertion bug can hide for weeks instead of failing immediately with a hermetic mock like MockWebServer. Engineers weighing live-backend E2E versus mocked mobile UI tests can compare approaches through daily.dev."}}]}
```

