【问题标题】:Mock location not working on Android Pie and later模拟位置不适用于 Android Pie 及更高版本
【发布时间】:2020-02-03 19:18:07
【问题描述】:

我编写了一个使用模拟位置提供程序的单元测试。接收到位置更新时测试通过,或超时时失败。

测试通过了在模拟的 Pixel 3、Android M 到 Android O 上的正常运行。它在 Android P 和 Q 上超时。我还在带有 Q 的物理 Pixel 3 上进行了测试,但仍然失败。

我已经为此苦苦挣扎了一段时间,无法弄清楚发生了什么。

测试:

import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.location.Location
import android.location.LocationManager
import androidx.test.rule.GrantPermissionRule
import design.inhale.androidtestutils.InstrumentedTest
import design.inhale.datasource.observer.NullDataSourceListener
import design.inhale.testutils.Latch
import design.inhale.utils.locationManager
import design.inhale.utils.mock
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.core.Is
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class DeviceLocationSourceTest: InstrumentedTest() {
    private val mockProviderName = "MockProvider"
    private val locationManager: LocationManager
    get() = appContext.locationManager

    @get:Rule
    val permissionRule: GrantPermissionRule = grant(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION)

    @Before
    fun init() {
        addMockLocationProvider()
    }

    @After
    fun deinit() {
        instrumentation.waitForIdleSync()
        removeMockLocationProvider()
    }

    @Test(timeout = 10_000)
    fun receiveLocationUpdate() {
        val latch = Latch()

        val listener = object: LocationSourceListener {

            override fun onDataUpdated(data: Location) {
                with(data) {
                    assertThat(latitude, Is(equalTo(0.0)))
                    assertThat(longitude, Is(equalTo(0.0)))
                }

                latch.release()
            }
        }

        mockLocationSource(listener).start()
        instrumentation.waitForIdleSync() // in case we're hitting race conditions?

        updateMockLocation(0.0, 0.0)

        latch.await()
    }

    @Suppress("SameParameterValue")
    private fun updateMockLocation(latitude: Double, longitude: Double) {
        val location = Location(mockProviderName).mock(latitude, longitude)
        locationManager.setTestProviderLocation(mockProviderName, location)
    }

    private fun mockLocationSource(listener: LocationSourceListener = NullDataSourceListener()) =
            DeviceLocationSource(appContext, mockProviderName, listener)

    private fun addMockLocationProvider() {
        with(locationManager) {
            try {
                addTestProvider(
                        mockProviderName,
                        false,
                        false,
                        false,
                        false,
                        true,
                        true,
                        true,
                        0,
                        5)
            } catch (e: IllegalArgumentException) {
                // If this is caught, the mock provider already exists
            }

            setTestProviderEnabled(mockProviderName, true)
        }
    }

    private fun removeMockLocationProvider() = locationManager.removeTestProvider(mockProviderName)
}

清单:

<manifest package="design.inhale.locationapi"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

androidTest/清单:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    package="design.inhale.locationapi"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>

</manifest>

Location.mock():

@SuppressLint("ObsoleteSdkInt")
fun Location.mock(latitude: Double = 0.0, longitude: Double = 0.0, accuracy: Float = 0f): Location {

    this.latitude = latitude
    this.longitude = longitude
    this.accuracy = accuracy
    this.time = currentTimeMillis()

    if (SDK_INT > JELLY_BEAN) this.elapsedRealtimeNanos = elapsedRealtimeNanos()

    return this
}

【问题讨论】:

    标签: android unit-testing testing mocking location


    【解决方案1】:

    原来我需要添加后台位置访问权限(我猜测试算作在后台运行?)。

    为测试清单添加了权限。

    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>`
    

    并更改测试以授予它:

    val permissionRule: GrantPermissionRule = grant(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION)
    

    请注意,您需要确保不要将 ACCESS_BACKGROUND_LOCATION 添加到生产清单中。如果 Google 发现您在没有正当理由的情况下使用此权限,他们将撤消您的应用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-01
      • 2018-01-09
      • 2014-06-14
      • 1970-01-01
      • 1970-01-01
      • 2016-12-05
      相关资源
      最近更新 更多