MockWebServer抛出IllegalStateException: start()已调用,求问题排查
我尝试使用MockWebServer开展UI测试,通过模拟接口响应来验证登录等UI的有效/无效状态变化。但每次运行代码都会抛出异常:java.lang.IllegalStateException: start() already called。
代码如下:
@RunWith(AndroidJUnit4.class) public class UITestPlayground { String testUrl = "http://testurl.com/"; MockWebServer server = new MockWebServer(); @Rule public IntentsTestRule<LoginActivity> mIntentsRule = new IntentsTestRule<>(LoginActivity.class); @Before public void beforeHelper() throws IOException { TestHelper.removeUserAndTokenIfAny(mIntentsRule.getActivity()); URLS.baseUrl = testUrl; server.url(URLS.baseUrl); //try to shutting down the server JUT IN CASE... server.shutdown(); server.start(); } @After public void afterHelper() throws IOException { server.shutdown(); } @Test public void invalidLoginDueNotValidJSONResponse() { server.enqueue(new MockResponse().setBody("Something not valid JSON response")); String emailToBeTyped = "tester@tester.com"; String passToBeTyped = "passtest"; ViewActions.closeSoftKeyboard(); // Type text and then press the button. onView(withId(R.id.login_email_edit)).perform(typeText(emailToBeTyped)); ViewActions.closeSoftKeyboard(); onView(withId(R.id.login_pass_edit)).perform(typeText(passToBeTyped)); ViewActions.closeSoftKeyboard(); onView(withId(R.id.log_in_btn)).perform(click()); //TODO: check on valid error message appearing } }
我仅调用了一次start(),甚至提前执行了shutdown()以防万一,但仍出现该异常,请问我哪里操作有误?
咱们一步一步拆解这个问题:
1. 核心问题:server.url()会隐式启动服务器
你大概率没注意到MockWebServer的一个隐藏行为:当你调用server.url()方法时,如果服务器还未启动,它会自动帮你调用start()。所以你在beforeHelper()里先执行了server.url(URLS.baseUrl),这时候服务器已经被悄悄启动了,之后你再手动调用server.start(),自然就会触发start() already called的异常。
你后面加的server.shutdown()其实没起作用——因为url()启动服务器后,紧接着调用shutdown()关闭,再start(),看似逻辑通顺,但这里还有个隐形风险:如果shutdown()还没完全完成就调用start(),也可能出现异常。
2. 修复步骤
方案一:调整调用顺序,手动控制启动时机
把server.start()放在server.url()之前,并且不要硬编码测试地址,直接用服务器生成的真实地址:
@Before public void beforeHelper() throws IOException { TestHelper.removeUserAndTokenIfAny(mIntentsRule.getActivity()); // 先手动启动服务器 server.start(); // 获取服务器的真实baseUrl(包含自动分配的端口) URLS.baseUrl = server.url("/").toString(); }
这样server.url("/")会基于已启动的服务器生成地址,不会再隐式启动,彻底避免重复启动的问题。
方案二:用JUnit Rule自动管理服务器生命周期
更优雅的方式是把MockWebServer做成JUnit Rule,让框架自动帮你处理启动和关闭,完全不用手动写start()和shutdown():
@Rule public MockWebServerRule mockWebServerRule = new MockWebServerRule(); @Before public void beforeHelper() throws IOException { TestHelper.removeUserAndTokenIfAny(mIntentsRule.getActivity()); // 直接通过Rule获取服务器实例,并获取baseUrl URLS.baseUrl = mockWebServerRule.server().url("/").toString(); } // 不需要再写@After方法关闭服务器,Rule会自动处理
这种方式从根源上避免了手动管理生命周期可能出现的错误,推荐使用。
补充:优化服务器关闭逻辑
如果坚持手动管理,建议在@After里加个判断,避免关闭未启动的服务器:
@After public void afterHelper() throws IOException { if (server != null && server.isRunning()) { server.shutdown(); } }
内容的提问来源于stack exchange,提问作者Adam Varhegyi

