Turbine is a small testing library for kotlinx.coroutines Flow.

Related tags

Testing turbine
Overview

Turbine

Turbine is a small testing library for kotlinx.coroutines Flow.

flowOf("one", "two").test {
  assertEquals("one", awaitItem())
  assertEquals("two", awaitItem())
  awaitComplete()
}

A turbine is a rotary mechanical device that extracts energy from a fluid flow and converts it into useful work.

Wikipedia

Download

repositories {
  mavenCentral()
}
dependencies {
  testImplementation 'app.cash.turbine:turbine:0.6.0'
}
Snapshots of the development version are available in Sonatype's snapshots repository.

repositories {
  maven {
    url 'https://oss.sonatype.org/content/repositories/snapshots/'
  }
}
dependencies {
  testImplementation 'app.cash.turbine:turbine:0.7.0-SNAPSHOT'
}

Usage

The entrypoint for the library is the test extension for Flow which accepts a validation block. Like collect, test is a suspending function that will not return until the flow is complete or canceled.

someFlow.test {
  // Validation code here!
}

Consuming Events

Inside the test block you must consume all received events from the flow. Failing to consume all events will fail your test.

flowOf("one", "two").test {
  assertEquals("one", awaitItem())
}
Exception in thread "main" AssertionError:
  Unconsumed events found:
   - Item(two)
   - Complete

As the exception indicates, consuming the "two" item is not enough. The complete event must also be consumed.

flowOf("one", "two").test {
  assertEquals("one", awaitItem())
  assertEquals("two", awaitItem())
  awaitComplete()
}

Received events can be explicitly ignored, however.

flowOf("one", "two").test {
  assertEquals("one", awaitItem())
  cancelAndIgnoreRemainingEvents()
}

Additionally, we can receive the most recent emitted item and ignore the previous ones.

no emission yet // 100ms - 200ms -> "one" is emitted // 200ms - 300ms -> "two" is emitted // 300ms - 400ms -> "three" is emitted delay(250) assertEquals("two", expectMostRecentItem()) cancelAndIgnoreRemainingEvents() } ">
flowOf("one", "two", "three")
  .map {
    delay(100)
    it
  }
  .test {
    // 0 - 100ms -> no emission yet
    // 100ms - 200ms -> "one" is emitted
    // 200ms - 300ms -> "two" is emitted
    // 300ms - 400ms -> "three" is emitted
    delay(250)
    assertEquals("two", expectMostRecentItem())
    cancelAndIgnoreRemainingEvents()
  }

Consuming Errors

Unlike collect, a flow which causes an exception will still be exposed as an event that you must consume.

flow { throw RuntimeException("broken!") }.test {
  assertEquals("broken!", awaitError().message)
}

Failure to consume an error will result in the same unconsumed event exception as above, but with the exception added as the cause so that the full stacktrace is available.

flow { throw RuntimeException("broken!") }.test { }
java.lang.AssertionError: Unconsumed events found:
 - Error(RuntimeException)
    at app.cash.turbine.ChannelBasedFlowTurbine.ensureAllEventsConsumed(FlowTurbine.kt:240)
    ... 53 more
Caused by: java.lang.RuntimeException: broken!
    at example.MainKt$main$1.invokeSuspend(Main.kt:7)
    ... 32 more

Asynchronous Flows

Calls to awaitItem(), awaitComplete(), and awaitError() are suspending and will wait for events from asynchronous flows.

channelFlow {
  withContext(IO) {
    Thread.sleep(100)
    send("item")
  }
}.test {
  assertEquals("item", awaitItem())
  awaitComplete()
}

By default, when one of the "await" methods suspends waiting for an event it will timeout after one second.

channelFlow {
  withContext(IO) {
    Thread.sleep(2_000)
    send("item")
  }
}.test {
  assertEquals("item", awaitItem())
  awaitComplete()
}
Exception in thread "main" TimeoutCancellationException: Timed out waiting for 1000 ms

A longer timeout can be specified as an argument to test.

channelFlow {
  withContext(IO) {
    Thread.sleep(2_000)
    send("item")
  }
}.test(timeout = 3.seconds) {
  assertEquals("item", awaitItem())
  awaitComplete()
}

Asynchronous flows can be canceled at any time and will not require consuming a complete or error event.

channelFlow {
  withContext(IO) {
    repeat(10) {
      Thread.sleep(200)
      send("item $it")
    }
  }
}.test {
  assertEquals("item 0", awaitItem())
  assertEquals("item 1", awaitItem())
  assertEquals("item 2", awaitItem())
  cancel()
}

Hot Flows

Emissions to hot flows that don't have active consumers are dropped. It's important to call test (and therefore have an active collector) on a flow before emissions to a flow are made. For example:

val mutableSharedFlow = MutableSharedFlow<Int>(replay = 0)
mutableSharedFlow.emit(1)
mutableSharedFlow.test {
  assertEquals(awaitItem(), 1)
  cancelAndConsumeRemainingEvents()
}

will fail with a timeout exception.

kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
	(Coroutine boundary)
	at app.cash.turbine.ChannelBasedFlowTurbine$awaitEvent$2.invokeSuspend(FlowTurbine.kt:238)
	at app.cash.turbine.ChannelBasedFlowTurbine$withTimeout$2.invokeSuspend(FlowTurbine.kt:206)
	at app.cash.turbine.ChannelBasedFlowTurbine.awaitItem(FlowTurbine.kt:243)

Proper usage of Turbine with hot flows looks like the following.

val mutableSharedFlow = MutableSharedFlow<Int>(replay = 0)
mutableSharedFlow.test {
  mutableSharedFlow.emit(1)
  assertEquals(awaitItem(), 1)
  cancelAndConsumeRemainingEvents()
}

The hot flow types Kotlin currently provide are:

  • MutableStateFlow
  • StateFlow
  • MutableSharedFlow
  • SharedFlow
  • Channels converted to flow with Channel.consumeAsFlow

Experimental API Usage

Turbine uses Kotlin experimental APIs:

  • Duration is used to declare the event timeout.

Since the library targets test code, the impact and risk of any breaking changes to these APIs are minimal and would likely only require a version bump.

Instead of sprinkling the experimental annotations or @OptIn all over your tests, opt-in at the compiler level.

Groovy DSL

compileTestKotlin {
  kotlinOptions {
    freeCompilerArgs += [
        '-Xopt-in=kotlin.time.ExperimentalTime',
    ]
  }
}

Kotlin DSL

tasks.compileTestKotlin {
  kotlinOptions {
    freeCompilerArgs += listOf(
        "-Xopt-in=kotlin.time.ExperimentalTime",
    )
  }
}

For multiplatform projects:

Groovy DSL

kotlin {
  sourceSets.matching { it.name.endsWith("Test") }.all {
    it.languageSettings {
      useExperimentalAnnotation('kotlin.time.ExperimentalTime')
    }
  }
}

Kotlin DSL

kotlin.sourceSets.matching {
  it.name.endsWith("Test")
}.configureEach {
  languageSettings.useExperimentalAnnotation("kotlin.time.ExperimentalTime")
}

License

Copyright 2018 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • Is this the intended use with StateFlow?

    Is this the intended use with StateFlow?

    Recently I used turbine to test some StateFlow's and came across a particular interaction and I'm mostly wondering what is the intended use. Both of the below cases fail.

    oven.preWarm(300)
    oven.states.test { state ->
      assertEquals(WARMING(100), expectItem())
      assertEquals(WARMING(200), expectItem())
      assertEquals(PREWARM_COMPLETE, expectItem())
    
      cancelAndIgnoreRemainingEvents()
    }
    

    For this case calling expectItem() only receives the final state expected (PREWARM_COMPLETE) from the preWarm() call and will fail the assertions. This makes sense as the collect operator is starting when our StateFlow has already emitted up to this state.

    oven.states.test { state ->
      assertEquals(WARMING(100), expectItem())
      assertEquals(WARMING(200), expectItem())
      assertEquals(PREWARM_COMPLETE, expectItem())
    
      cancelAndIgnoreRemainingEvents()
    }
    
    oven.preWarm(300)
    

    For this case there is an exception for a default timeout: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms

    or for timeout = Duration.ZERO java.lang.IllegalStateException: This job has not completed yet


    However, when used like the below, moving the call to trigger state emission to the validate lambda, all expected events are received and the test passes. This is because validate is called after collection starts on the receiver flow.

    oven.states.test { state ->
      oven.preWarm(300)
      assertEquals(WARMING(100), expectItem())
      assertEquals(WARMING(200), expectItem())
      assertEquals(PREWARM_COMPLETE, expectItem())
    
      cancelAndIgnoreRemainingEvents()
    }
    

    And also launching in a new job and calling test and the emission after succeeds as well.

    launch {
      oven.states.test { state ->
        assertEquals(WARMING(100), expectItem())
        assertEquals(WARMING(200), expectItem())
        assertEquals(PREWARM_COMPLETE, expectItem())
    
        cancelAndIgnoreRemainingEvents()
      }
    
      oven.preWarm(300)
    }
    

    The documentation provided so far doesn't provide much information for a best practice on how to handle Flows that have their emissions triggered elsewhere. What is the best practice here? I would assume that launching a new job should not be required.


    Note: I am using this rule to runBlockingTests

    @ExperimentalCoroutinesApi
    class MainCoroutineRule(
        val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher(),
    ) : TestWatcher() {
    
        override fun starting(description: Description?) {
            super.starting(description)
            Dispatchers.setMain(testDispatcher)
        }
    
        override fun finished(description: Description?) {
            super.finished(description)
            Dispatchers.resetMain()
            testDispatcher.cleanupTestCoroutines()
        }
    
        /**
         * A convenience function to prevent the need to call a long chain for the provided dispatcher
         */
        fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) {
            testDispatcher.runBlockingTest { block() }
        }
    }
    
    documentation 
    opened by himattm 15
  • TestScheduler delay behavior test

    TestScheduler delay behavior test

    Would be great if time based operator tests could make full use of TestScheduler delay skipping behavior.

    If I remove the Unconfined override everything but legacy JS passes. image

    Index: src/commonMain/kotlin/app/cash/turbine/FlowTurbine.kt
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    ===================================================================
    diff --git a/src/commonMain/kotlin/app/cash/turbine/FlowTurbine.kt b/src/commonMain/kotlin/app/cash/turbine/FlowTurbine.kt
    --- a/src/commonMain/kotlin/app/cash/turbine/FlowTurbine.kt	(revision 55b82684930ae9a67e53e57e632c3bb640b474f8)
    +++ b/src/commonMain/kotlin/app/cash/turbine/FlowTurbine.kt	(date 1652572185509)
    @@ -140,7 +140,7 @@
     private fun <T> Flow<T>.collectTurbineIn(scope: CoroutineScope): ChannelBasedFlowTurbine<T> {
       val events = Channel<Event<T>>(UNLIMITED)
     
    -  val collectJob = scope.launch(start = UNDISPATCHED, context = Unconfined) {
    +  val collectJob = scope.launch(start = UNDISPATCHED) {
         val terminalEvent = try {
           if (debug) println("Collect starting!")
           collect { item ->
    
    opened by trevjonez 9
  • Cannot test SharedFlow

    Cannot test SharedFlow

    I have an android viewmdeol class with the following property

    private val _trainingNavigationEvents = MutableSharedFlow<NavigationEventTraining>(replay = 0)
        val trainingNavigationEvents = _trainingNavigationEvents.asSharedFlow()
    
    fun navigate(navigationEvent: NavigationEventTraining) {
            viewModelScope.launch {
                _trainingNavigationEvents.emit(navigationEvent)
            }
        }
    

    I am using a SharedFlow as it solves the SingleLiveEvent problem.

    The issue arises when I try and unit test the code. I can't see how to use turbine (or supplied primitives) to get it to work.

        @ExperimentalTime
        @Test
        fun `navigate`() = runBlockingTest {
            viewModel.handleIntent(TrainingViewModel.TrainingIntent.ShowQuestions)
    
            viewModel.navigationEvents.test {
                assertEquals(
                    TrainingViewModel.TrainingNavigationEvent.NavigateToQuestions::class,
                    expectItem()::class
                )
                cancelAndConsumeRemainingEvents()
            }
        }
    

    and I get

    kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
    

    I know that a SharedFlow never completes and that may be part of the reason but I have been unable to find any examples of how to do this instead.

    I am using Junit 5 and am using a TestCoroutineDispatcher class extension.

    opened by rakesh-sh2020 9
  • Different behaviour when using coroutines 1.6.0

    Different behaviour when using coroutines 1.6.0

    As an example I would like to suggest following test:

    private val stateFlow = MutableStateFlow(0)
    private val flow: Flow<Int> = stateFlow
    
    @Test
    fun `turbine test`() = runBlockingTest {
        Dispatchers.setMain(TestCoroutineDispatcher())
    
        val scope = CoroutineScope(Job() + Dispatchers.Main)
    
        flow.test {
            flowOf(1, 2, 3)
                .onEach { stateFlow.value = it }
                .launchIn(scope)
    
            val events = cancelAndConsumeRemainingEvents()
        }
    
        Dispatchers.resetMain()
    }
    

    When using coroutines 1.5.2, as expected events variable will be a list with size of 4 which contains integers wrapped in Item class: [Item(0), Item(1), Item(2), Item(3)]

    But when using coroutines 1.6.0 like so:

    private val stateFlow = MutableStateFlow(0)
    private val flow: Flow<Int> = stateFlow
    
    @Test
    fun `turbine test`() = runTest {
        Dispatchers.setMain(UnconfinedTestDispatcher())
    
        val scope = CoroutineScope(Job() + Dispatchers.Main)
    
        flow.test {
            flowOf(1, 2, 3)
                .onEach { stateFlow.value = it }
                .launchIn(scope)
    
            val events = cancelAndConsumeRemainingEvents()
        }
    
        Dispatchers.resetMain()
    }
    

    events will contain only the first and last items: [Item(0), Item(3)].

    Also if I use StandardTestDispatcher instead of UnconfinedTestDispatcher, events will contain only the first item: [Item(0)].

    Is it expected behaviour? Do I need to adjust my test somehow to get the same results as for coroutines version 1.5.2?

    opened by alterpie 8
  • Use test-specific unconfined when test scheduler is in use

    Use test-specific unconfined when test scheduler is in use

    This retains the test scheduler virtual time.

    Unfortunately this requires taking a dependency on the test library and also relying on unstable API.

    Closes #124. Closes #118.

    opened by JakeWharton 7
  • AdvanceTimeBy has no effect within test block when using runBlockingTest

    AdvanceTimeBy has no effect within test block when using runBlockingTest

    Pretty simple repro with the following test:

        @Test
        fun test() = runBlockingTest {
            val flow = flow {
                delay(100L)
                emit("a")
            }
    
            flow.test {
                advanceTimeBy(101L)
                assertThat(expectItem()).isEqualTo("a")
            }
        }
    

    However, the test above fails with the following error:

    kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
    	at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:186)
    	at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:156)
    	at kotlinx.coroutines.test.TimedRunnable.run(TestCoroutineDispatcher.kt)
    	at kotlinx.coroutines.test.TestCoroutineDispatcher.doActionsUntil(TestCoroutineDispatcher.kt:103)
    	at kotlinx.coroutines.test.TestCoroutineDispatcher.advanceUntilTime(TestCoroutineDispatcher.kt:123)
    	at kotlinx.coroutines.test.TestCoroutineDispatcher.advanceUntilIdle(TestCoroutineDispatcher.kt:133)
    	at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:52)
    	at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest$default(TestBuilders.kt:45)
    	at com.repro.ReproTest.test(ReproTest.kt:16)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    

    I suspect this is probably some nuance with TestCoroutineDispatcher plus the fact that test { … } is internally using Dispatchers.Unconfined.

    Kotlin: 1.4.31 Coroutines: 1.4.3 Turbine: 0.4.1

    opened by mhernand40 7
  • Recompile a new version with Kotlin 1.5.0

    Recompile a new version with Kotlin 1.5.0

    This is not a request before stable Kotlin but at the moment turbine doesn't work with kotlin 1.5.0-RC because of experimental time api changes. There is binary incompatibility between 1.4 and 1.5 about Duration class.

    Issue reported to Kotlin and resolved as designed and suggested to build library with 1.5. https://youtrack.jetbrains.com/issue/KT-46254

    opened by kozaxinan 7
  • GH releases do not have binaries attached

    GH releases do not have binaries attached

    github-release is a separate job from the publish job. When binaries are collected by action-gh-release step they are not present in the directory.

    https://github.com/cashapp/turbine/blob/39740b4dad4977de427e1bf43121570ad0f2a618/.github/workflows/release.yaml#L54-L58

    https://github.com/cashapp/turbine/runs/1024853027?check_suite_focus=true#step:5:10

    Since github-release job needs publish anyway they could be merged into a one job. This is just a suggestion as I don't know if there is a better way to do this.

    On the side note, it would be nice to add binaries to already published GH releases retroactively.

    pr welcome 
    opened by MiSikora 7
  • Figure out if we want automatic cancelation at the end of a test block

    Figure out if we want automatic cancelation at the end of a test block

    Right now a test block requires that you cancel, cancel and ignore events, or consume a terminal event before the test block will complete successfully. This creates a very explicit script that details the lifecycle of the underlying collection operation, but with infinite flows (such as those from SQLDelight or presenters) it means every block ends with cancel().

    What are the implications of automatically canceling? We would still validate all received events were consumed. cancel() would almost certainly still be an API so that you could explicitly control it. We could rename cancelAndIgnoreRemainingEvents() to just ignoreRemainingEvents().

    opened by JakeWharton 7
  • `cancelAndIgnoreRemainingEvents()` does not cancel a Molecule StateFlow

    `cancelAndIgnoreRemainingEvents()` does not cancel a Molecule StateFlow

    This might be Molecule-specific, but I still expect cancelAndIgnoreRemainingEvents() to be able to cancel any StateFlow.

    Here's a simple Molecule presenter:

    @Composable fun MoleculePresenter(): Model {
      return Model("Hello!")
    }
    

    And a test for it:

    class MoleculePresenterTest {
      @Test fun test() = runBlocking {
        makePresenter().test {
          assertEquals(Model("Hello!"), awaitItem())
          cancelAndIgnoreRemainingEvents()
        }
      }
    
      private fun CoroutineScope.makePresenter(): StateFlow<Model> {
        return launchMolecule(RecompositionClock.Immediate) {
          MoleculePresenter()
        }
      }
    }
    

    test() never completes when executed, neither with cancelAndIgnoreRemainingEvents() nor without it.

    The repro project is here: https://github.com/Egorand/turbine-cancel-hangs. Simply run ./gradlew app:testDebugUnitTest to reproduce the issue.

    (Crappy) workaround:

    @Test(expected = CancellationException::class)
    fun test() = runBlocking {
      makePresenter().test {
        assertEquals(Model("Hello!"), awaitItem())
        coroutineContext.cancel()
      }
    }
    
    opened by Egorand 6
  • Add skip functionality

    Add skip functionality

    Sometimes it might be good to have a way to skip a certain number of events to have a cleaner test without multiple await calls, which are then not part of any assertion. For my codebase I added an extension function like this:

    suspend fun <T> FlowTurbine<T>.skipItem(amount: Int = 1) = repeat(amount) {
        awaitItem()
    }
    

    Does it make sense to add that functionality on library level?

    opened by JohannesPtaszyk 6
  • Update dependency com.vanniktech:gradle-maven-publish-plugin to v0.23.1

    Update dependency com.vanniktech:gradle-maven-publish-plugin to v0.23.1

    Mend Renovate

    This PR contains the following updates:

    | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | com.vanniktech:gradle-maven-publish-plugin | 0.14.2 -> 0.23.1 | age | adoption | passing | confidence |


    Release Notes

    vanniktech/gradle-maven-publish-plugin

    v0.23.1

    Compare Source

    • Also support publishing sources for the java-test-fixtures plugin in Kotlin/JVM projects.
    • Suppress Gradle warnings when publishing a project that uses java-test-fixtures.

    v0.23.0

    Compare Source

    Updated docs can be found on the new website.

    • NEW: It is now possible to set group id, artifact id directly through the DSL
      mavenPublishing {
        coordinates("com.example", "library", "1.0.3")
      }
      
    • project.group and project.version will still be used as default values for group and version if the GROUP/VERSION_NAME Gradle properties do not exist and coordinates was not called, however there are 2 behavior changes:
      • The GROUP and VERSION_NAME Gradle properties take precedence over project.group and project.version instead of being overwritten by them. If you need to define the properties but replace them for some projects, please use the new coordinates method instead.
      • The GROUP and VERSION_NAME Gradle properties will not be explicitly set as project.group and project.version anymore.
    • NEW: Added dropRepository task that will drop a Sonatype staging repository. It is possible to specify which repository to drop by adding a --repository parameter with the id of the staging repository that was printed during publish. If no repository is specified and there is only one staging repository, that one will be dropped.
    • Added workaround to also publish sources for the java-test-fixtures plugin
    • Fixed publishing Kotlin/JS projects with the base plugin.
    • Fixed that a POM configured through the DSL is incomplete when publishing Gradle plugins.
    • The minimum supported Gradle version has been increased to 7.3.

    v0.22.0

    Compare Source

    • NEW: When publishing to maven central by setting SONATYPE_HOST or calling publishToMavenCentral(...) the plugin will now explicitly create a staging repository on Sonatype. This avoids issues where a single build would create multiple repositories
    • The above change means that the plugin supports parallel builds and it is not neccessary anymore to use --no-parallel and --no-daemon together with publish
    • NEW: When publishing with the publish or publishAllPublicationsToMavenCentralRepository tasks the plugin will automatically close the staging repository at the end of the build if it was successful.
    • NEW: Option to also automatically release the staging repository after closing was susccessful
    SONATYPE_HOST=DEFAULT # or S01
    SONATYPE_AUTOMATIC_RELEASE=true
    

    or

    mavenPublishing {
      publishToMavenCentral("DEFAULT", true)
      // or publishToMavenCentral("S01", true)
    }
    
    • in case the option above is enabled, the closeAndReleaseRepository task is not needed anymore
    • when closing the repository fails the plugin will fail the build immediately instead of timing out
    • when closing the repository fails the plugin will try to print the error messages from Nexus
    • increased timeouts for calls to the Sonatype Nexus APIs
    • fixed incompatibility with the com.gradle.plugin-publish plugin
    • added wokaround for Kotlin multiplatform builds reporting disabled build optimizations (see KT-46466)

    v0.21.0

    Compare Source

    Minimum supported Gradle version is now 7.2.0

    Minimum supported Android Gradle Plugin versions are now 7.1.2, 7.2.0-beta02 and 7.3.0-alpha01

    Behavior changes

    The com.vanniktech.maven.publish stops adding Maven Central (Sonatype OSS) as a publishing target and will not enable GPG signing by default. To continue publishing to maven central and signing artifacts either add the following to your gradle.properties:

    SONATYPE_HOST=DEFAULT
    

    v0.20.0

    Compare Source

    Upcoming behavior change

    In the next release after this the com.vanniktech.maven.publish will stop adding Maven Central (Sonatype OSS) as a publishing target and will not enable GPG signing by default. If you are currently relying on this behavior the plugin will print a warning during configuration phase. To continue publishing to maven central and signing artifacts either add this to your build files:

    mavenPublishing {
      publishToMavenCentral() // use publishToMavenCentral("S01") for publishing through s01.oss.sonatype.org
      signAllPublications()
    }
    

    or the following to your gradle.properties:

    SONATYPE_HOST=DEFAULT
    

    v0.19.0

    Compare Source

    • Behavior Change: When using version 7.1.0 or newer of the Android Gradle Plugin we will now publish all variants of a library unless androidVariantToPublish was set in the DSL. This means that for example both debug and release or all flavors.
    • Deprecated androidVariantToPublish. In the future the main plugin will always publish all variants of an Android library. If you need to publish only one variant or a subset take a look at the base plugin APIs.
    • Base plugin: Added AndroidSingleVariantLibrary and AndroidMultiVariantLibrary options that use the new AGP 7.1 APIs under the hood.
    • Base plugin: Deprecated AndroidLibrary option in favor of the above
    • The integration with Sonatype Nexus has been extracted into it's own artifact and is available as com.vanniktech:nexus:<version>

    v0.18.0

    Compare Source

    • The minimum supported Kotlin version is now 1.4.30
    • It's now possible to specify SONATYPE_HOST as a Gradle property, e.g.
      • SONATYPE_HOST=S01 for s01.sonatype.org
      • SONATYPE_HOST= to not add any repository by default
    • Fixed an issue when publishing Kotlin MPP projects with the base plugin
    • Removed checks for presence of properties that aren't used by this plugin anymore

    v0.17.0

    Compare Source

    • Removed the deprecated uploadArchives and installArchives tasks. Use publish and publishToMavenLocal instead.

    v0.16.0

    Compare Source

    • Add pomFromGradleProperties API to base plugin. This configures the pom in the same way the regular plugin does.
    • Add the ability to remove the default mavenCentral repository, by setting sonatypeHost to null
    • Support POM_LICENSE_NAME, POM_LICENSE_URL and POM_LICENSE_DIST properties in addition to LICENCE based properties.
    • Fixes an issue in the base plugin that caused an error during configuration of Android projects.
    • Fixes an issue with javadoc tasks when using Java toolchains.
    • The deprecated nexusOptions and nexus {} methods were removed. closeAndReleaseRepository is automatically configured.

    v0.15.1

    Compare Source

    • The closeAndReleaseRepository task was mistakenly expecting the wrong Gradle properties. The README and changelog also mentioned the wrong properties. The correct ones are mavenCentralUsername and mavenCentralPassword or for environment variables: ORG_GRADLE_PROJECT_mavenCentralUsername and ORG_GRADLE_PROJECT_mavenCentralPassword.
    • Fix signing not being configurable until afterEvaluate
    • Use empty string as in memory signing password when no password is specified
    • Fix statingProfile in nexusOptions not being optional which causes an error when running closeAndReleaseRepository

    v0.15.0

    Compare Source

    • BREAKING: Removed support for deprecated RELEASE_REPOSITORY_URL, SNAPSHOT_REPOSITORY_URL, SONATYPE_NEXUS_USERNAME, SONATYPE_NEXUS_PASSWORD environment variables and properties. For safety reasons the project will fail when finding these. Use mavenCentralUsername and mavenCentralPassword Gradle properties or ORG_GRADLE_PROJECT_mavenCentralUsername and ORG_GRADLE_PROJECT_mavenCentralPassword environment variables instead.
    • BREAKING: Removed deprecated targets API. See README for alternative ways of adding targets.
    • Behavior change: The dokka plugin is not applied by default anymore for Kotlin projects. When it is applied we will still use the dokka tasks to create the javadoc jar.
    • Support for s01.oss.sonatype.org by setting sonatypeHost = "S01".
    • Introduce com.vanniktech.maven.publish.base plugin. This plugin contains all the functionality of the main plugin, but does not configure anything automatically. Instead, it offers a public API, which is also used by the main plugin to do so yourself. This allows for more flexibility and to publish different project types. The API is not final yet, but we're happy to receive feedback.

    Configuration

    📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

    🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

    Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

    🔕 Ignore: Close this PR and you won't be reminded about this update again.


    • [ ] If you want to rebase/retry this PR, check this box

    This PR has been generated by Mend Renovate. View repository job log here.

    opened by renovate[bot] 0
  • Dependency Dashboard

    Dependency Dashboard

    This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

    Open

    These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

    Detected dependencies

    github-actions
    .github/workflows/build.yaml
    • actions/checkout v3
    • gradle/wrapper-validation-action v1
    • actions/setup-java v3.9.0
    .github/workflows/release.yaml
    • actions/checkout v3
    • gradle/wrapper-validation-action v1
    • actions/setup-java v3.9.0
    • ffurrer2/extract-release-notes v1
    • softprops/action-gh-release v1
    gradle
    gradle.properties
    build.gradle
    • org.jetbrains.kotlin:kotlin-gradle-plugin 1.8.0
    • com.diffplug.spotless:spotless-plugin-gradle 6.12.1
    • com.vanniktech:gradle-maven-publish-plugin 0.14.2
    • org.jetbrains.dokka:dokka-gradle-plugin 1.7.20
    gradle-wrapper
    gradle/wrapper/gradle-wrapper.properties
    • gradle 7.6

    • [ ] Check this box to trigger a request for Renovate to run again on this repository
    opened by renovate[bot] 0
  • Errors are obscured when interacting with multiple Turbines

    Errors are obscured when interacting with multiple Turbines

    In the following test:

     @Test fun failingTest() = runTest {
       val otherTurbine = Turbine<String>()
       flow<Nothing> {
         throw RuntimeException("I barfed!")
       }.test {
         assertEquals("next item", otherTurbine.awaitItem())
       }
     }
    

    ...the programmer never sees the RuntimeException failure. They only see the assertion error from otherTurbine.

    This can be really, really confusing when the RuntimeException is the root cause of the problem.

    feature 
    opened by jingibus 5
  • How can I test back-pressure?

    How can I test back-pressure?

    Hi, I was wondering if there is any mechanism to test the same back-pressure behavior that collect() has. Here is an example on the different behaviors I encounter.

    @Test
    public fun `when I test - flow should backpressure`(): TestResult = runTest {
        val myFlow:Flow<Int> = flow {
            println("emitting 1")
            emit(1)
            println("emitted 1")
            delay(10)
            println("emitting 2")
            emit(2)
            println("emitted 2")
            delay(10)
            println("emitting 3")
            emit(3)
            println("emitted 3")
        }
    
    
        println("COLLECT")
        myFlow.collect {
            println("collected $it")
            delay(50)
        }
    
        println()
        println("TEST")
        // Test
        myFlow.test {
            val item1 = awaitItem()
            println("collected $item1")
            delay(50)
            val item2 = awaitItem()
            println("collected $item2")
            delay(50)
            val item3 = awaitItem()
            println("collected $item3")
            awaitComplete()
        }
    }
    

    Prints

    COLLECT
    emitting 1
    collected 1
    emitted 1
    emitting 2
    collected 2
    emitted 2
    emitting 3
    collected 3
    emitted 3
    
    TEST
    emitting 1
    emitted 1
    emitting 2
    emitted 2
    emitting 3
    emitted 3
    collected 1
    collected 2
    collected 3
    
    opened by caiofaustino 8
  • `ChannelTurbine` uses unstable APIs via `@OptIn`

    `ChannelTurbine` uses unstable APIs via `@OptIn`

    We need to pursue stabilization upstream and track it.

    • SendChannel.isClosedForSend (https://github.com/Kotlin/kotlinx.coroutines/issues/3448)
    • TestCouroutineScheduler / UnconfinedTestScheduler (no upstream issue)
    blocked 
    opened by JakeWharton 0
Releases(0.12.1)
  • 0.12.1(Nov 3, 2022)

    Changed

    • Build with Kotlin 1.7.20

    Fixed

    • takeItem() no longer throws an exception when returning a null value.
    • await-prefixed methods no longer interfere with virtual time control from a TestScheduler (such as inside runTest).
    Source code(tar.gz)
    Source code(zip)
  • 0.12.0(Oct 11, 2022)

    Added

    • Support specifying a human-readable name for differentiating the failures of multiple Turbines

    Fixed

    • Properly catch all Throwable subtypes from failures in flows and channels as events.
    Source code(tar.gz)
    Source code(zip)
  • 0.11.0(Sep 21, 2022)

    Added

    • Restore timeout support. By default a 1-second timeout will be enforced when awaiting an event. This can be customized by supplying a timeout argument or by using the withTurbineTimeout wrapper function. Timeouts will always use wall clock time even when using a virtual time dispatcher.

    Changed

    • When runTest (or any TestCoroutineScheduler) is in use, switch to the UnconfinedTestScheduler internally to ensure virtual time remains working.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.0(Sep 14, 2022)

    Changed

    • Remove ReceiveTurbine.ignoreRemainingEvents from public API.

    Fixed

    • Restore usage of Unconfined dispatcher preventing value conflation (as much as possible) so that intermediate values can always be observed.
    Source code(tar.gz)
    Source code(zip)
  • 0.9.0(Aug 1, 2022)

    • FlowTurbine is now called ReceiveTurbine. This is the consume-only type with which you assert on events it has seen (historically only from a Flow).
    • New public Turbine type implements ReceiveTurbine but also allows you write events from a data source. Use this to implement fakes or collect events from non-Flow streams.
    • Extension functions on ReceiveChannel provide ReceiveTurbine-like assertion capabilities.
    • Support for legacy JS has been removed. Only JS IR is now supported.
    • Removed some APIs deprecated in 0.8.x.
    Source code(tar.gz)
    Source code(zip)
  • 0.8.0(May 2, 2022)

    Added

    • New testIn(CoroutineScope) API allows testing multiple flows without nesting lambdas.
    • New skip(Int) API can replace one or more calls to awaitItem() where the result is not needed.

    Changed

    • Removed timeout parameter. The new runTest API from kotlinx.coroutines enforces a timeout automatically.
    • Documented that flows are implicitly canceled at the end of the test lambda. This has been the behavior for a few versions by accident, but now it is explicit and documented.
    • Cancel (and friends) are now suspending functions to ensure that non-canceleable coroutines complete and their effects are observed deterministically.
    Source code(tar.gz)
    Source code(zip)
  • 0.7.0(Oct 26, 2021)

    Changed

    • Moved APIs using Kotlin's experimental time to separate extensions. You can now use the library without worrying about incompatibilities with Kotlin version or coroutine library version.
    • Removed APIs deprecated in 0.6.x.
    Source code(tar.gz)
    Source code(zip)
  • 0.6.1(Sep 2, 2021)

  • 0.6.0(Jul 27, 2021)

    Added

    • expectMostRecentItem() function consumes all received items and returns the most recent item.

    Changed

    • Functions which may suspend to wait for an event are now prefixed with 'await'.
    Source code(tar.gz)
    Source code(zip)
  • 0.5.2(Jun 3, 2021)

  • 0.5.1(May 18, 2021)

  • 0.5.0(May 14, 2021)

  • 0.5.0-rc1(May 10, 2021)

  • 0.4.1(Mar 15, 2021)

  • 0.4.0(Feb 9, 2021)

  • 0.3.0(Nov 23, 2020)

    Added

    • cancelAndConsumeRemainingEvents() cancels the Flow and returns any unconsumed events which were already received.
    • expectEvent() waits for an event (item, complete, or error) and returns it as a sealed type Event.
    Source code(tar.gz)
    Source code(zip)
  • 0.2.1(Aug 25, 2020)

  • 0.2.0(Aug 17, 2020)

  • 0.1.1(Aug 3, 2020)

  • 0.1.0(Aug 3, 2020)

Powerful, elegant and flexible test framework for Kotlin with additional assertions, property testing and data driven testing

Kotest is a flexible and comprehensive testing tool for Kotlin with multiplatform support. To learn more about Kotest, visit kotest.io or see our quic

Kotest 3.8k Jan 3, 2023
Android UI Testing

User scenario testing for Android Robotium is an Android test automation framework that has full support for native and hybrid applications. Robotium

null 2.8k Dec 14, 2022
A set of AssertJ helpers geared toward testing Android.

AssertJ Android A set of AssertJ assertions geared toward testing Android. Deprecated The support libraries and play services are developing at a rate

Square 1.6k Jan 3, 2023
A programmer-oriented testing framework for Java.

JUnit 4 JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks. For more infor

JUnit 8.4k Jan 9, 2023
Android Unit Testing Framework

Robolectric is the industry-standard unit testing framework for Android. With Robolectric, your tests run in a simulated Android environment inside a

Robolectric 5.6k Jan 3, 2023
Android UI Testing

User scenario testing for Android Robotium is an Android test automation framework that has full support for native and hybrid applications. Robotium

null 2.7k Apr 8, 2021
A programmer-oriented testing framework for Java.

JUnit 4 JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks. For more infor

JUnit 8.4k Dec 28, 2022
A sample repo describing best practices for snapshot testing on Android

Road to effective snapshot testing A sample repo describing best practices for snapshot testing on Android. This includes for now: Parameterized Tests

Sergio Sastre Flórez 86 Jan 1, 2023
Fixtures for Kotlin providing generated values for unit testing

A tool to generate well-defined, but essentially random, input following the idea of constrained non-determinism.

Appmattus Limited 191 Dec 21, 2022
Morsa: Jetpack Compose UI Testing Framework

Morsa: Jetpack Compose UI Testing Framework Test library to ease UI testing with Jetpack Compose Purpose This library aims to add some useful wrappers

HyperDevs 10 Dec 3, 2022
Lovely Systems Database Testing

Lovely DB Testing This repository provides opinionated testing helpers for database testing used at Lovely Systems. License This plugin is made availa

Lovely Systems GmbH 1 Feb 23, 2022
Project for testing intern candidate simple level

RickAndMorty-TestTask Тестовый проект 'Гайд по мультфильму Рик и Морти' для практикантов начального и продвинутого уровня. Структура проекта Структура

null 0 Nov 18, 2021
Very simple Morse API text translator. Mainly to do some testing with jitpack, API development etc.

Very simple Morse text translator API. Far from finish or even being reliable. Use for single sentence. Mainly to test github dependency for Android

Piotr Dymala 0 Dec 30, 2021
Snapshot Testing framework for Kotlin.

KotlinSnapshot Snapshot Testing framework for Kotlin. What is this? Snapshot testing is an assertion strategy based on the comparision of the instance

Pedro Gómez 157 Nov 13, 2022
A set of TestRules and ActivityScenarios to facilitate UI Testing under given configurations: FontSizes, Locales

AndroidUiTestingUtils A set of TestRules, ActivityScenarios and utils to facilit

Sergio Sastre Flórez 122 Dec 23, 2022
Testify — Android Screenshot Testing

Testify — Android Screenshot Testing Add screenshots to your Android tests Expand your test coverage by including the View-layer. Testify allows you t

ndtp 43 Dec 24, 2022
Toster - Small test dsl based on adb commands that allows you to test the mobile application close to user actions

toster Small test dsl based on adb commands that allows you to test the mobile a

Alexander Kulikovskiy 31 Sep 1, 2022
Android Sample Kotlin+ MVVM + Coroutines + Retrofit +Hilt+ Junit + Mockito

NTimes AppSample NY Times Most Popular Articles simple app to hit the NY Times Most Popular Articles API and show a list of articles, that shows detai

Amer Elsayed 0 Dec 27, 2021
Android library that allows you to run your acceptance tests written in Gherkin in your Android instrumentation tests.

Green Coffee Green Coffee is a library that allows you to run your acceptance tests written in Gherkin in your Android instrumentation tests using the

Mauricio Togneri 227 Nov 21, 2022