You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mockito:如何处理从已有对象生成新对象的方法测试问题?

解决Android Location子类单元测试中"Method not mocked"报错问题

问题原因

Android本地单元测试(JVM环境)默认使用的android.jar中,SDK类(如android.location.Location)的方法大多是空Stub实现,调用未被Stub的方法就会抛出类似Method setTime in android.location.Location not mocked的异常。你的MyLocation继承自Location,构造新实例时触发了Location内部未Stub的方法,因此报错。这不是反模式,是Android单元测试的默认限制导致的常见问题。

解决方案

1. 抽象依赖,解耦Android SDK类

把Location的核心属性(纬度、经度等)抽象成独立接口,让MyLocation依赖接口而非直接继承Location。这样测试时完全避开Android SDK类,只验证计算逻辑:

// 抽象位置数据接口
public interface LocationData {
    double getLatitude();
    double getLongitude();
}

// 自定义位置类,实现接口
public class MyLocation implements LocationData {
    private final double latitude;
    private final double longitude;

    // 从Android Location转换的构造器
    public MyLocation(Location location) {
        this.latitude = location.getLatitude();
        this.longitude = location.getLongitude();
    }

    // 用于计算的构造器
    public MyLocation(double latitude, double longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public MyLocation move(double speed, double dir, int seconds) {
        double deltaLat = Math.cos(dir) * speed * seconds;
        double deltaLng = Math.sin(dir) * speed * seconds;
        return new MyLocation(latitude + deltaLat, longitude + deltaLng);
    }

    @Override
    public double getLatitude() {
        return latitude;
    }

    @Override
    public double getLongitude() {
        return longitude;
    }
}

测试时直接构造MyLocation实例,验证计算结果即可,完全不会触发Android SDK的方法:

@Test
public void testMoveCalculation() {
    MyLocation initialLoc = new MyLocation(0.0, 0.0);
    MyLocation newLoc = initialLoc.move(10, Math.PI/2, 1);

    assertEquals(0.0, newLoc.getLatitude(), 0.001);
    assertEquals(10.0, newLoc.getLongitude(), 0.001);
}

2. 使用Robolectric提供真实Android类实现

Robolectric可以在JVM测试中提供Android SDK类的真实实现,无需Mock就能让Location的方法正常执行。在测试类上添加@RunWith(RobolectricTestRunner.class),直接构造MyLocation实例即可:

@RunWith(RobolectricTestRunner.class)
public class MyLocationTest {
    @Test
    public void testMove() {
        MyLocation initialLoc = new MyLocation("test"); // 使用Location的合法构造器
        initialLoc.setLatitude(0.0);
        initialLoc.setLongitude(0.0);

        MyLocation newLoc = initialLoc.move(10, Math.PI/2, 1);

        assertEquals(0.0, newLoc.getLatitude(), 0.001);
        assertEquals(10.0, newLoc.getLongitude(), 0.001);
    }
}

3. Mock Location的必要方法(临时方案)

如果不想修改原有代码,可以在测试中用Mockito StubLocation的所有必要方法,避免报错。但这种方法需要Stub多个方法,比较繁琐:

@RunWith(MockitoJUnitRunner.class)
public class MyLocationTest {
    @Mock
    Location mockLocation;

    @Test
    public void testMove() {
        // Stub Location的核心方法
        when(mockLocation.getLatitude()).thenReturn(0.0);
        when(mockLocation.getLongitude()).thenReturn(0.0);
        // Stub构造时会调用的setTime方法
        doNothing().when(mockLocation).setTime(anyLong());
        // 若还有其他未Stub方法报错,需继续添加Stub

        MyLocation myLoc = new MyLocation(mockLocation);
        MyLocation newLoc = myLoc.move(10, Math.PI/2, 1);

        assertEquals(0.0, newLoc.getLatitude(), 0.001);
        assertEquals(10.0, newLoc.getLongitude(), 0.001);
    }
}

总结

优先选择抽象依赖的方案,它能彻底解耦Android SDK,让测试更纯净、易维护;如果需要保留与Location的继承关系,推荐用Robolectric;Mock方法仅作为临时应急方案。

内容的提问来源于stack exchange,提问作者user998303

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 14:05:35