This chapter explains how to make reactive Android code testable and reliable by structuring it around small, composable, mostly pure modules. It contrasts unit and acceptance testing as ends of a spectrum: the closer you are to pure data and deterministic logic, the more you should rely on unit tests; the closer you are to UI and integration, the more acceptance tests make sense. Using a “pyramid of dependencies,” it argues for a wide base of small, well-tested building blocks composed into larger features, while treating external dependencies (APIs, platform components, third‑party libraries) as risky areas that warrant defensive design and testing.
After revisiting unit testing fundamentals (assemble/arrange, act, assert), the chapter shows how to test RxJava code by treating reactive chains like functions with predictable inputs and outputs. It introduces TestObserver to capture emissions, errors, and termination, with assertions such as assertComplete, assertError, assertNotTerminated, assertValue, and value count checks. A key simplifier is that Rx chains run synchronously unless you explicitly change threads; therefore, let high‑level code decide threading and keep operators thread‑agnostic for easy tests. Mockito is used to mock collaborators and verify side effects, while RxJava’s own testing tools cover most needs; time‑dependent flows can be tested with TestScheduler when necessary.
Practical examples walk through testing a custom observable (e.g., a sort that emits results), then move to mid‑level components like view models. Inputs are mocked as reactive sources (BehaviorSubject for stateful streams, PublishSubject for events) and functions are mocked to verify invocations, enabling checks of both emitted data and side effects. To keep tests focused and deterministic, complex chains from a view model are extracted into static utility functions and tested in isolation, illustrated with Connect Four’s “drop” logic. The chapter closes with guidance on choosing what to test—prioritize deterministic logic, isolate external concerns, and design for testability—so changes elsewhere won’t silently break reactive behavior.
Introducing test subscribers
Trying to test a badly behaving function
GameViewModelTest.java
GameViewModelTest.java
GameUtils.java
GameViewModel.java subscribe function
GameUtilsTest.java
FAQ
What’s the difference between unit tests and acceptance tests in reactive Android apps?Unit tests focus on small, isolated pieces (preferably pure functions), avoid Android framework classes, and aim for high coverage. Acceptance tests check how parts work together (possibly end-to-end), may use framework components, and don’t try to cover every path. Smoke tests are a minimal subset of acceptance tests that verify critical flows like app start or login.How should I structure a unit test for reactive code?Use the Arrange–Act–Assert pattern: set up inputs and dependencies (Arrange), execute the function or chain (Act), then verify a single expected outcome (Assert). Keep each test focused on one behavior to maintain clarity.How do I test an Observable chain’s outputs?Treat the chain as a function from inputs to outputs. Subscribe a TestObserver, run the chain, then assert on what the observer saw: counts, values, completion, or errors. This lets you inspect emissions without side effects in subscribe.Which TestObserver assertions help verify completion and errors?Use assertComplete() to confirm normal termination, assertError(...) to check a specific error type or instance, and assertNotTerminated() when the source should neither complete nor error (e.g., an open stream).How can I assert emitted values from an Observable?Use assertValue(value) when exactly one value is expected, assertValueCount(n) for the number of emissions, and values() (or getValues()) to retrieve the ordered list and make detailed assertions on its contents.Do I need to handle threading in unit tests for RxJava?By default, reactive chains run synchronously unless you specify subscribeOn/observeOn. Prefer keeping thread hops out of low-level functions so tests remain synchronous and deterministic. For truly time-dependent operations, use TestScheduler to control virtual time.How do I mock dependencies and verify interactions?Add Mockito to test dependencies, create mocks for side-effecting functions (e.g., a Consumer that updates state), and verify calls, including verify(mock, never()).apply(any()) to ensure no unintended updates occurred.What should I test in a reactive architecture?Favor testing pure, small modules that form the base of your “pyramid of dependencies.” Minimize and isolate external factors (APIs, platform classes, third-party libs). For larger components (like view models), focus on key behaviors and critical paths rather than exhaustive coverage.How do I unit test a view model that uses reactive streams?Provide controllable inputs: BehaviorSubject for stateful streams, PublishSubject for events, and a mocked function for side effects. Attach TestObservers to outputs. Use a @Before setup to initialize dependencies, then drive inputs and assert outputs and interactions.How do I set up and run RxJava tests with Mockito on Android?Add Mockito to the test configuration (e.g., org.mockito:mockito-core), rely on RxJava’s built-in testing tools (TestObserver, TestScheduler), and run tests with ./gradlew test. Keep chains synchronous in tests unless you explicitly control schedulers.
pro $24.99 per month
access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!