Testing¶
Sandwich provides a dedicated testing module called sandwich-test that offers fake factories and an assertion DSL for testing ApiResponse handling in your domain, repository, and presentation layers without requiring any HTTP infrastructure (no MockWebServer, no Ktor MockEngine).
To utilize the testing utilities, add the following dependency:
dependencies {
testImplementation "com.github.skydoves:sandwich-test:$version"
}
dependencies {
testImplementation("com.github.skydoves:sandwich-test:$version")
}
For Kotlin Multiplatform, add the dependency below to your module's build.gradle.kts file:
sourceSets {
val commonTest by getting {
dependencies {
implementation("com.github.skydoves:sandwich-test:$version")
}
}
}
Fake Factories¶
The sandwich-test module provides factory functions on ApiResponse.Companion to create deterministic ApiResponse instances for testing. Unlike the production factories (ApiResponse.of, ApiResponse.exception), these factories bypass SandwichInitializer global operators and failure mappers, ensuring predictable test behavior.
fakeSuccess¶
Creates a fake ApiResponse.Success with the given data and optional tag:
val response = ApiResponse.fakeSuccess(data = listOf("Frozen", "Moana"))
val tagged = ApiResponse.fakeSuccess(data = user, tag = "cache")
fakeError¶
Creates a fake ApiResponse.Failure.Error with an optional payload:
val response = ApiResponse.fakeError(payload = "Not found")
val withStatusCode = ApiResponse.fakeError(payload = StatusCode.NotFound)
val empty = ApiResponse.fakeError()
fakeException¶
Creates a fake ApiResponse.Failure.Exception from a Throwable or a message string:
val response = ApiResponse.fakeException(throwable = IOException("timeout"))
val fromMessage = ApiResponse.fakeException(message = "network error")
Assertion DSL¶
The assertion DSL provides type-safe extensions on ApiResponse that eliminate manual instanceOf checks and unsafe casts. Each assertion returns the narrowed type for chaining. The assertions throw AssertionError on failure, making them compatible with any test framework (kotlin.test, JUnit 4/5, Kotest).
Type Assertions¶
Assert the response type and get the narrowed instance in one call:
val success = response.assertSuccess()
assertEquals("Frozen", success.data.name)
val error = response.assertError()
assertEquals("Not found", error.payload)
val exception = response.assertException()
assertIs<IOException>(exception.throwable)
val failure = response.assertFailure() // matches both Error and Exception
Block-Style Assertions¶
Assert the type and run custom assertions within a scoped block:
response.assertSuccess {
assertEquals("Frozen", data.name)
assertNotNull(tag)
}
response.assertError {
assertEquals(StatusCode.Forbidden, payload)
}
response.assertException {
assertEquals("timeout", message)
}
response.assertFailure {
// executes for both Error and Exception
}
Convenience Assertions¶
Common one-liner assertions for the most frequent checks:
// Assert success and verify data equality
response.assertSuccessData(expectedUser)
// Assert exception and verify message
response.assertExceptionMessage("timeout")
Complete Testing Example¶
Here's a practical example of testing a repository that uses Sandwich:
class UserRepositoryTest {
private val fakeApi = FakeUserApi()
private val repository = UserRepository(fakeApi)
@Test
fun fetchUserReturnsUserOnSuccess() {
fakeApi.response = ApiResponse.fakeSuccess(data = User("Alice", 30))
val result = repository.fetchUser("alice")
result.assertSuccess {
assertEquals("Alice", data.name)
assertEquals(30, data.age)
}
}
@Test
fun fetchUserReturnsErrorOnNotFound() {
fakeApi.response = ApiResponse.fakeError(payload = "user not found")
val result = repository.fetchUser("unknown")
result.assertError {
assertEquals("user not found", payload)
}
}
@Test
fun fetchUserReturnsExceptionOnNetworkFailure() {
fakeApi.response = ApiResponse.fakeException(message = "network timeout")
val result = repository.fetchUser("alice")
result.assertExceptionMessage("network timeout")
}
}