我有一个Activity
,它在onCreate()
生命周期方法中执行API调用。如果此调用失败(在调试或测试环境中通常会失败),ViewStub
会膨胀,充当错误屏幕。这个ViewStub
涵盖了Activity
的其余部分。
使用Espresso执行UI测试时会出现问题。我希望能够取消或控制此请求的结果,以便我可以为它编写可预测的测试。
以下是Activity
中的API代码:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// ...
// Perform call
viewModel.loadStuff()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> progressBar.setVisibility(View.VISIBLE))
.doOnComplete(() -> progressBar.setVisibility(View.GONE))
.subscribe(response -> {
// Success
// ...
}, throwable -> {
// Fail
throwable.printStackTrace();
errorStub.inflate();
});
}
我写的一个Espresso测试是在几秒钟后测试此Activity
中按钮的启用状态。但是,由于上述API调用在测试时失败,该按钮被ViewStub
覆盖,测试失败。
我尝试通过编写自定义成功响应脚本来实现OkHTTP3的MockWebServer
(注意应用程序使用Retrofit,而不是直接使用OkHTTP)。但是,似乎要使用MockWebServer
,您必须使用它返回的自定义URL,并让您的应用程序代码使用该URL进行调用。这意味着修改应用程序代码以适应测试代码,这是没有意义的。
我也听说过自定义Dagger设置来解决这个问题。
问题:
Activity
的{{1}}方法中执行的API调用可以取消或控制?onCreate
正确用于此案例,那么如何在不修改应用程序代码的情况下使用它呢?赞赏与此问题相关的一般性评论和提示。
答案 0 :(得分:2)
如何设置测试以便在API中执行API调用 Activity的onCreate方法可以无效或控制吗?
您的viewModel.loadStuff()
似乎与某些预先定义的网址硬连线。为了使其可测试,我将在viewmodel创建期间将url作为参数传递或将其传递给loadStuff
方法。我将演示第二种选择。
接下来的问题是我们如何在测试期间更改网址?一种方法是将URL存储在Application
实例中并覆盖测试值。
<强> 1。创建包含url值的应用程序类。
class MyApplication extends Application {
private static final String DEFAULT_URL = "http://yourdomain.com/api";
private String url;
@Override
void onCreate() {
super.onCreate();
url = DEFAULT_URL;
}
public String getUrl() {
return url;
}
@VisibleForTesting
public void setUrl(String newUrl) {
url = newUrl
}
}
<强> 2。解析活动中的网址值
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// resolve url value
String url = ((MyApplication) getApplication()).getUrl();
// Perform call
viewModel.loadStuff(url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> progressBar.setVisibility(View.VISIBLE))
.doOnComplete(() -> progressBar.setVisibility(View.GONE))
.subscribe(response -> {
// Success
// ...
}, throwable -> {
// Fail
throwable.printStackTrace();
errorStub.inflate();
});
}
第3。在测试中,您可以覆盖URL以使用MockWebServer的URL。
// Espresso Test
public class ActivityTest {
@Before
public void setUp() throws Exception {
MockWebServer webServer = new MockWebServer();
HttpUrl url = webServer.url("/");
// set the url to point to mock webserver in localhost
MyApplication app = (MyApplication) InstrumentationRegistry.getTargetContext().getApplicationContext();
app.setUrl(url.toString());
}
....
}
您还可以使用共享首选项来存储服务器URL并在测试期间覆盖该值,但想法是相同的: 您的视图模型不应该硬连线到某个URL,而是将URL注入视图模型。 为了解决这样的依赖性,Dagger是个不错的选择。
答案 1 :(得分:0)
我建议使用MockWebServer(还有Wiremock但设置稍微复杂一点,所以让我们坚持使用 MockWebServer )并模拟 Dagger 模块。
<强> 1。添加模拟应用程序和模块
首先,我们需要使api模块独立于 baseUrl 。我们将有 BaseUrlProvider 两个实现(仅用于ui测试的普通和模拟)。
@Module
class ApiModule {
@Provides
fun provideRetrofit(baseUrlProvider: BaseUrlProvider): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrlProvider.baseUrl())
.builc()
}
@Provides
fun provideMyApi(retrofit: Retrofit): MyApi {
return retrofit.create(MyApi::class.java)
}
}
我们需要提供 BaseUrlProvider 的模拟实现。 Mock baseUrl 可以存储在测试应用程序类中。
@Module
abstract class MockBaseUrlModule {
@Binds
abstract fun bindBaseUrlProvider(baseUrlProvider: MockBaseUrlProvider): BaseUrlProvider
}
class MockBaseUrlProvider(private val context: Context) : BaseUrlProvider {
override fun baseUrl: String {
return (context as MockApplication).baseUrl
}
}
class MockApplication : MyApplication() {
var baseUrl: String = ""
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerMockAppComponent.builder().build()
}
}
自定义运行器负责创建正在测试的应用程序实例。
class MockRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
return super.newApplication(cl, MockApplication::class.java.name, context)
}
}
android {
defaultConfig {
testInstrumentationRunner "com.foo.MockRunner"
}
}
<强> 2。添加规则
我们需要启动模拟服务器并在应用程序中保存模拟 baseUrl 。
class MockWebServerRule : ExternalResource() {
val server: MockWebServer = MockWebServer()
override fun before() {
server.start()
val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as MockApplication
app.baseUrl = server.url("/").toString()
}
override fun after() {
server.shutdown()
}
}
第3。添加测试
请求在活动 onCreate 中生成,因此需要在模拟响应后手动启动活动。
class MyActivityTest {
@Rule
@JvmField
val activityRule = ActivityTestRule(MyActivity::class.java, false, null)
@Rule
@JvmField
val mockWebServerRule = MockWebServerRule()
@Test
fun testFailure() {
mockWebServerRule.server.enqueue(MockResponse().setResponseCode(HttpURLConnection.HTTP_BAD_REQUEST))
activityRule.launchActivity(null)
//assert view
}
}