Skip to content

Commit e511336

Browse files
committed
Add more tests
1 parent ebf7d04 commit e511336

File tree

7 files changed

+749
-76
lines changed

7 files changed

+749
-76
lines changed

docs/TESTING_INSTRUMENTED.md

Lines changed: 88 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
[Back to README](../README.md)
44

5-
This document explains instrumented testing for OpenMapView. Currently, the project does not have instrumented tests, but this guide describes when and how to add them.
5+
This document explains instrumented testing for OpenMapView, including setup and the existing test suite.
66

77
## Overview
88

99
**Instrumented tests** (also called **instrumentation tests** or **on-device tests**) run on an Android emulator or physical device. They provide access to real Android framework APIs and hardware.
1010

11-
**Current Status:** OpenMapView has **no instrumented tests** yet. All testing is done via [unit tests with Robolectric](TESTING_UNIT.md).
11+
**Current Status:** OpenMapView has **9 instrumented tests** (2 for TileDownloader, 7 for MapController) that test real rendering, network operations, and Canvas drawing. Unit tests with Robolectric (72 tests) cover logic and calculations.
1212

1313
## Unit Tests vs Instrumented Tests
1414

@@ -49,60 +49,84 @@ Continue using unit tests (with Robolectric) for:
4949
mkdir -p openmapview/src/androidTest/kotlin/de/afarber/openmapview
5050
```
5151

52-
### 2. Add Dependencies
52+
### 2. Dependencies (Already Configured)
5353

54-
Update `openmapview/build.gradle.kts`:
54+
The project has instrumented testing dependencies configured in `openmapview/build.gradle.kts`:
5555

5656
```kotlin
5757
dependencies {
58-
// Existing dependencies...
59-
60-
// Instrumented testing
61-
androidTestImplementation("androidx.test.ext:junit:1.1.5")
62-
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
63-
androidTestImplementation("androidx.test:runner:1.5.2")
64-
androidTestImplementation("androidx.test:rules:1.5.0")
58+
// Instrumentation testing
59+
androidTestImplementation("androidx.test:core-ktx:1.6.1")
60+
androidTestImplementation("androidx.test:runner:1.6.2")
61+
androidTestImplementation("androidx.test:rules:1.6.1")
62+
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
63+
androidTestImplementation("junit:junit:4.13.2")
64+
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
6565
}
6666
```
6767

68-
### 3. Example Instrumented Test
68+
### 3. Current Instrumented Tests
6969

70-
Create `openmapview/src/androidTest/kotlin/de/afarber/openmapview/OpenMapViewInstrumentedTest.kt`:
70+
The project has instrumented tests in `openmapview/src/androidTest/kotlin/de/afarber/openmapview/`:
7171

72+
**TileDownloaderInstrumentationTest.kt** (2 tests):
7273
```kotlin
73-
package de.afarber.openmapview
74-
75-
import android.content.Context
76-
import androidx.test.core.app.ApplicationProvider
77-
import androidx.test.ext.junit.runners.AndroidJUnit4
78-
import org.junit.Assert.assertEquals
79-
import org.junit.Assert.assertNotNull
80-
import org.junit.Test
81-
import org.junit.runner.RunWith
82-
8374
@RunWith(AndroidJUnit4::class)
84-
class OpenMapViewInstrumentedTest {
75+
class TileDownloaderInstrumentationTest {
8576
@Test
86-
fun testOpenMapViewCreation() {
87-
val context = ApplicationProvider.getApplicationContext<Context>()
88-
val mapView = OpenMapView(context)
77+
fun testDownloadRealOsmTile() = runTest {
78+
val downloader = TileDownloader()
79+
val tileUrl = TileSource.STANDARD.getTileUrl(TileCoordinate(x = 0, y = 0, zoom = 0))
80+
val result = downloader.downloadTile(tileUrl)
81+
82+
assertNotNull("Should successfully download a real OSM tile", result)
83+
result?.let {
84+
assert(it.width > 0) { "Downloaded bitmap should have width > 0" }
85+
assert(it.height > 0) { "Downloaded bitmap should have height > 0" }
86+
}
87+
downloader.close()
88+
}
89+
}
90+
```
8991

90-
assertNotNull(mapView)
91-
assertEquals(0, mapView.childCount) // FrameLayout with no children initially
92+
**MapControllerInstrumentationTest.kt** (7 tests):
93+
```kotlin
94+
@RunWith(AndroidJUnit4::class)
95+
class MapControllerInstrumentationTest {
96+
private lateinit var controller: MapController
97+
98+
@Before
99+
fun setUp() {
100+
val context = InstrumentationRegistry.getInstrumentation().targetContext
101+
controller = MapController(context)
102+
controller.setViewSize(1080, 1920)
92103
}
93104

94105
@Test
95-
fun testBitmapRendering() {
96-
// Test actual bitmap rendering with real Android framework
97-
val bitmap = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)
106+
fun testDraw_WithRealCanvas() {
107+
val bitmap = Bitmap.createBitmap(1080, 1920, Bitmap.Config.ARGB_8888)
108+
val canvas = Canvas(bitmap)
109+
110+
controller.setCenter(LatLng(51.4661, 7.2491))
111+
controller.setZoom(14.0)
112+
controller.draw(canvas)
98113

99114
assertNotNull(bitmap)
100-
assertEquals(48, bitmap.width)
101-
assertEquals(72, bitmap.height)
115+
assertTrue(bitmap.width == 1080)
116+
assertTrue(bitmap.height == 1920)
117+
}
102118

103-
// Verify actual pixel colors (not possible with Robolectric)
104-
val centerPixel = bitmap.getPixel(24, 36)
105-
// Assert red-ish color (exact value depends on marker design)
119+
@Test
120+
fun testDraw_WithMarkers() {
121+
val bitmap = Bitmap.createBitmap(1080, 1920, Bitmap.Config.ARGB_8888)
122+
val canvas = Canvas(bitmap)
123+
124+
controller.setCenter(LatLng(51.4661, 7.2491))
125+
controller.setZoom(14.0)
126+
controller.addMarker(Marker(LatLng(51.4661, 7.2491)))
127+
controller.draw(canvas)
128+
129+
assertNotNull(bitmap)
106130
}
107131
}
108132
```
@@ -136,24 +160,28 @@ adb devices
136160

137161
## Test Categories for OpenMapView
138162

139-
### Recommended Instrumented Tests
163+
### Current Instrumented Tests
164+
165+
1. **Rendering Tests** (Implemented)
166+
- Real Canvas drawing with actual Bitmap
167+
- Marker rendering with real Android graphics
168+
- Zoom and pan rendering validation
140169

141-
1. **Rendering Tests**
142-
- Verify tile rendering produces non-null bitmaps
143-
- Check marker icon pixel colors
144-
- Test canvas drawing operations
170+
2. **Network Tests** (Implemented)
171+
- Real OSM tile downloads
172+
- Network error handling
145173

146-
2. **Touch Gesture Tests**
174+
3. **Lifecycle Tests** (Implemented)
175+
- MapController lifecycle integration
176+
177+
### Future Instrumented Tests
178+
179+
1. **Touch Gesture Tests**
147180
- Pan gesture recognition
148181
- Pinch-to-zoom gesture
149182
- Double-tap zoom
150183

151-
3. **Integration Tests**
152-
- Full map initialization
153-
- Tile downloading and caching
154-
- Marker addition and rendering
155-
156-
4. **Performance Tests**
184+
2. **Performance Tests**
157185
- Measure frame rate during panning
158186
- Memory usage with many markers
159187
- Tile cache eviction behavior
@@ -210,16 +238,16 @@ jobs:
210238
211239
## Test Structure
212240
213-
Typical instrumented test structure:
241+
Current instrumented test structure:
214242
215243
```
216244
openmapview/src/androidTest/kotlin/de/afarber/openmapview/
217-
├── OpenMapViewTest.kt # View creation and basic functionality
218-
├── MapControllerRenderTest.kt # Rendering verification
219-
├── GestureHandlingTest.kt # Touch gestures
220-
└── MarkerIntegrationTest.kt # Marker display and interaction
245+
├── TileDownloaderInstrumentationTest.kt # Real OSM tile downloads (2 tests)
246+
└── MapControllerInstrumentationTest.kt # Canvas rendering and markers (7 tests)
221247
```
222248

249+
Total: 9 instrumented tests covering real Android framework behavior.
250+
223251
## Espresso UI Testing
224252

225253
For testing UI interactions, use Espresso:
@@ -255,16 +283,17 @@ class MapInteractionTest {
255283
| **Instrumented** | Real Android, hardware access, accurate | Slow, requires device, CI complexity |
256284
| **Hybrid** | Best of both worlds | More test code to maintain |
257285

258-
**Current OpenMapView approach:** Pure unit tests with Robolectric provide sufficient coverage for the current feature set.
286+
**Current OpenMapView approach:** Hybrid approach with 72 unit tests (Robolectric) for logic and 9 instrumented tests for real Android framework validation.
259287

260-
## Future Considerations
288+
## Expanding Instrumented Tests
261289

262-
Add instrumented tests when:
290+
Consider adding more instrumented tests when:
263291

264292
- Users report device-specific rendering issues
265-
- Adding complex gesture handling
266-
- Implementing hardware-dependent features
293+
- Adding complex gesture handling (currently tested via unit tests)
294+
- Implementing hardware-dependent features (GPS, sensors)
267295
- Validating performance on low-end devices
296+
- Testing pixel-perfect rendering accuracy
268297

269298
## References
270299

docs/TESTING_UNIT.md

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ openmapview/src/test/kotlin/de/afarber/openmapview/
2020
| **Kotlin Test** | (inherited) | Kotlin-specific test utilities |
2121
| **MockK** | 1.13.8 | Mocking framework for Kotlin |
2222
| **Robolectric** | 4.14 | Android framework shadow implementations |
23+
| **Ktor Client Mock** | 2.3.7 | HTTP client mocking for network tests |
24+
| **Coroutines Test** | 1.9.0 | Testing coroutines and async code |
2325

2426
## Running Tests
2527

@@ -48,14 +50,16 @@ The `--continue` flag ensures all tests run even if some fail, useful for gettin
4850

4951
## Test Structure
5052

51-
### Current Test Coverage (43 tests)
53+
### Current Test Coverage (72 tests)
5254

5355
| Test Class | Tests | Description |
5456
|------------|-------|-------------|
5557
| **BitmapDescriptorFactoryTest** | 7 | Marker icon generation with colors |
5658
| **MarkerTest** | 8 | Marker creation, equality, and properties |
5759
| **ProjectionTest** | 12 | Web Mercator projection calculations |
5860
| **TileCacheTest** | 6 | LRU bitmap caching |
61+
| **TileDownloaderTest** | 2 | HTTP tile downloading with mocked Ktor client |
62+
| **MapControllerTest** | 28 | Zoom, pan, marker management, touch detection |
5963
| **ViewportCalculatorTest** | 10 | Visible tile calculation |
6064

6165
### Example Test
@@ -102,8 +106,8 @@ class MyTest {
102106

103107
Use the `@RunWith(RobolectricTestRunner::class)` annotation when testing:
104108

105-
- Bitmap operations (`BitmapDescriptorFactoryTest`, `TileCacheTest`)
106-
- Canvas drawing (`MarkerIconFactory`)
109+
- Bitmap operations (`BitmapDescriptorFactoryTest`, `TileCacheTest`, `TileDownloaderTest`)
110+
- Canvas drawing (`MarkerIconFactory`, `MapControllerTest`)
107111
- View classes (if testing custom views)
108112
- Android API calls (Context, Resources, etc.)
109113

@@ -113,6 +117,20 @@ Do not use Robolectric for:
113117
- Data classes (`Marker`, `LatLng`, `TileCoordinate`)
114118
- Business logic without Android dependencies
115119

120+
### Testing Async Code with Coroutines
121+
122+
Use `kotlinx-coroutines-test` for testing suspend functions:
123+
124+
```kotlin
125+
@Test
126+
fun testAsyncOperation() = runTest {
127+
val result = myRepository.fetchData()
128+
assertNotNull(result)
129+
}
130+
```
131+
132+
The `runTest` function creates a test coroutine scope that automatically advances time.
133+
116134
### Configuration
117135

118136
Robolectric is configured in `openmapview/build.gradle.kts`:
@@ -136,26 +154,52 @@ dependencies {
136154
- `isReturnDefaultValues = true` - Android methods return default values instead of throwing exceptions
137155
- `isIncludeAndroidResources = true` - Makes Android resources available to tests
138156

139-
## MockK: Mocking Framework
157+
## MockK and Ktor MockEngine: Mocking Frameworks
158+
159+
### MockK for Application Classes
140160

141161
MockK is used for mocking Kotlin classes and interfaces. While Robolectric handles Android framework classes, MockK is used for application-level mocking.
142162

143-
### Current Usage
163+
**Current Usage:**
144164

145-
The project uses MockK minimally since Robolectric provides real Android implementations. MockK is available for mocking dependencies like:
165+
```kotlin
166+
@Test
167+
fun testDraw_ValidViewport() {
168+
val canvas = mockk<Canvas>(relaxed = true)
169+
controller.draw(canvas)
170+
verify(atLeast = 1) { canvas.drawRect(any(), any(), any(), any(), any()) }
171+
}
172+
```
173+
174+
### Ktor MockEngine for HTTP Tests
175+
176+
Use `io.ktor:ktor-client-mock` to test network code without making real HTTP calls:
146177

147178
```kotlin
148-
// Example: Mocking a repository
149-
val mockRepository = mockk<TileRepository>()
150-
every { mockRepository.getTile(any()) } returns mockBitmap
179+
@Test
180+
fun testDownloadTile_Success() = runTest {
181+
val mockBitmapBytes = createMockPngBytes()
182+
val mockEngine = MockEngine { request ->
183+
respond(
184+
content = ByteReadChannel(mockBitmapBytes),
185+
status = HttpStatusCode.OK,
186+
headers = headersOf(HttpHeaders.ContentType, "image/png")
187+
)
188+
}
189+
190+
val client = HttpClient(mockEngine)
191+
// Test with mocked client
192+
}
151193
```
152194

153-
### MockK vs Robolectric
195+
### Comparison Table
154196

155197
| Use Case | Tool | Example |
156198
|----------|------|---------|
157199
| Mock `Bitmap` operations | Robolectric (preferred) | Use real `Bitmap.createBitmap()` |
158-
| Mock `UserRepository` | MockK | Use `mockk<UserRepository>()` |
200+
| Mock `Canvas` drawing | MockK | `mockk<Canvas>(relaxed = true)` |
201+
| Mock HTTP requests | Ktor MockEngine | `MockEngine { respond(...) }` |
202+
| Mock `UserRepository` | MockK | `mockk<UserRepository>()` |
159203
| Test projection math | Neither | Pure unit tests |
160204

161205
## Test Reports
@@ -325,19 +369,24 @@ Check Robolectric version compatibility with CI's JDK version. The project uses:
325369

326370
## Test Coverage Goals
327371

328-
Current coverage focuses on:
372+
Current coverage includes:
329373
- Core projection math (Web Mercator)
330374
- Tile coordinate calculations
331375
- Marker API and bitmap generation
332376
- Tile caching logic
333377
- Viewport calculation
334-
335-
Future coverage should include:
336378
- MapController rendering logic
337-
- Touch gesture handling
338-
- Zoom level validation
379+
- Touch gesture handling (marker hit detection)
380+
- Zoom level validation and bounds
339381
- Network tile downloading (with mocking)
340-
- Error handling and edge cases
382+
- Pan offset calculations
383+
384+
Future coverage should include:
385+
- Disk cache implementation tests
386+
- Tile pre-fetching tests
387+
- Performance benchmarks
388+
- Memory usage tests
389+
- Error recovery scenarios
341390

342391
## References
343392

0 commit comments

Comments
 (0)