- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
注意:我使用 () 而不是尖括号 <> 作为数据类型,因为找不到让它们在论坛上工作的方法。
我有一个 MVP android 应用程序,它使用 Retofit2 和 RxJava2 从 GitHub Api 获取数据。代码工作正常,我能够恢复 Observable(Response(List(Headers))),其中 Response 来自 Retrofit2,Headers 来自 OkHttp3。
但是当涉及到单元测试时,我遇到了一个问题:我无法模拟 Response(List(Headers))。Retrofit2 Response 类有一个私有(private)构造函数,所以我无法创建它的实例。然后我尝试将 OkHttp MockWebServer 与具有 MockResponse(List(Headers)) 的想法一起使用。尽管我可以将 header 设置为 MockResponse 实例,但我无法获得 MockResponse(List(Headers))
我的服务:
@GET("users/{username}/repos")
Observable<Response<List<Headers>>> checkReposPerUser(@Path("username") String owner,
@Query("access_token") String accessTokenString,
@Query("token_type") String accessTokenTypeString,
@Query("per_page") String perPageValue);
我的主持人:
@Override
public void checkRepoPerUser(String owner) {
//recovering access token data from Shared Preferences;
String accessTokenString = repository.getAccessTokenString();
String accessTokenTypeString = repository.getAccessTokenType();
//Asking for a list of repositories with 1 repository per page.
//This let us know how many repositories we found and also to deal with error response code
Disposable disposable = repository.checkReposPerUser(owner, accessTokenString, accessTokenTypeString, "1")
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.subscribe(this::handleReturnedHeaderData, this::handleHeaderError);
disposeBag.add(disposable);
}
@VisibleForTesting
public void handleReturnedHeaderData(Response<List<Headers>> response) {
//getting value 'Link' from response headers in order to count the repositories
String link = response.headers().get("Link");
String message = response.message();
//checking GitHub API requests limit
String limit = response.headers().get("X-RateLimit-Limit");
Log.d(TAG, "Limit requests: " + limit);
String limitRemaining = response.headers().get("X-RateLimit-Remaining");
Log.d(TAG, "Limit requests remaining: " + limitRemaining);
//getting http response code
int code = response.code();
switch (code){
case 404:
if(message.equalsIgnoreCase("not found")){ //User not exists
view.showUserNotFoundMessage();
}else{
view.showErrorMessage(message);
}
break;
case 403:
//GitHub API requests limit reached
//Instead of showing an error, we start the login process,
// store another access token in shared Preferences and resend the same request that failed before
view.startLogin();
break;
case 200:
if(link == null){ //Link value is not present into the header, it means there's 0 or 1 repo
Log.d(TAG, "Total repos for current user is 0 or 1.");
//get the repository
searchRepo(view.getOwner()); //Starting looking for data
}else if( link != null){
//get last page number: considering that we requested all the repos paginated with
//only 1 repo per page, the last page number is equal to the total number of repos
String totalRepoString = link.substring(link.lastIndexOf("&page=") + 6, link.lastIndexOf(">"));
Log.d(TAG, "Total repos for current user are " + totalRepoString);
// TODO once we know how many repositories we have, we can decide how many calls to do (total repositories/100 rounded up )
//get the repositories
searchRepo(view.getOwner()); //Starting 3 looking for data
}
break;
default:
searchRepo(view.getOwner()); //Starting 3 looking for data
break;
}
}
现在,我想对 handleReturnedHeaderData(Response(List(Headers)) response) 进行单元测试。这是测试类:
public class RepositoriesPresenterTest {
private static final Repo REPO1 = new Repo();
private static final Repo REPO2 = new Repo();
private static final Repo REPO3 = new Repo();
private static final List<Repo> NO_REPOS = Collections.emptyList();
private static final List<Repo> THREE_REPOS = Arrays.asList(REPO1, REPO2, REPO3);
public static final String OWNER = "owner";
public static final String ACCESS_TOKEN_STRING = "access_token_string";
public static final String ACCESS_TOKEN_TYPE = "access_token_type";
public static final String PER_PAGE_VALUE = "per_page_value";
@Parameterized.Parameters
public static Object[] data() {
return new Object[] {NO_REPOS, THREE_REPOS};
}
@Parameterized.Parameter
public List<Repo> repos;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Mock private GitHubChallengeRepository repositoryMock;
@Mock private RepositoriesContract.View viewMock;
@Mock private Response<List<Headers>> responseListHeaders;
private TestScheduler testScheduler;
private RepositoriesPresenter SUT; //System Under Test
@Before public void setUp() {
testScheduler = new TestScheduler();
SUT = new RepositoriesPresenter(repositoryMock, viewMock, testScheduler, testScheduler);
}
}
我尝试进入 RepositoriesPresenterTest 类:
@Test public void repoPresenter_CheckRepoPerUser_responseHeadersListExpected() throws Exception {
// Mock a response with status 200 and some Headers
MockResponse mockResponse = new MockResponse()
.setResponseCode(200)
.setBody("{}")
.setHeader("Status", "status_value")
.setHeader("X-RateLimit-Limit", "60")
.setHeader("X-RateLimit-Remaining", "57");
List<Headers> headersList = Arrays.asList(mockResponse.getHeaders());
// Given
given(repositoryMock.getAccessTokenString()).willReturn(ACCESS_TOKEN_STRING);
given(repositoryMock.getAccessTokenType()).willReturn(ACCESS_TOKEN_TYPE);
given(repositoryMock.checkReposPerUser(
OWNER,
ACCESS_TOKEN_STRING,
ACCESS_TOKEN_TYPE,
PER_PAGE_VALUE)).willReturn((Observable<Response<List<Headers>>>) Observable.just(headersList));
// When
SUT.checkRepoPerUser(OWNER);
testScheduler.triggerActions();
// Then
then(viewMock).should().getOwner();
}
但它无法编译,因为我无法将 Observable.just(headersList) 转换为一个 Observable(Response(List(Headers)))。
之后我尝试使用 MockWebServer 来模拟连接(即使我认为测试没有必要):
@Test
public void repoPresenter_CheckRepoPerUser_responseHeadersListExpected() throws InterruptedException, IOException {
MockWebServer mockWebServer = new MockWebServer();
TestObserver testObserver = new TestObserver<Response<List<Headers>>>();
String path = "\"users/{username}/repos\"";
// Mock a response with status 200 and some Headers
MockResponse mockResponse = new MockResponse()
.setResponseCode(200)
.setBody("{}")
.setHeader("Status", "status_value")
.setHeader("X-RateLimit-Limit", "60")
.setHeader("X-RateLimit-Remaining", "57");
// Enqueue request
mockWebServer.enqueue(mockResponse);
// Call the API
repositoryMock.checkReposPerUser(
OWNER,
ACCESS_TOKEN_STRING,
ACCESS_TOKEN_TYPE,
PER_PAGE_VALUE).subscribe((Consumer<? super Response<List<Headers>>>) Observable.just(Arrays.asList(mockResponse.getHeaders()));
testScheduler.triggerActions();
testObserver.awaitTerminalEvent(2, TimeUnit.SECONDS);
// No errors
testObserver.assertNoErrors();
// Make sure we made the request to the required path
assertEquals("60", mockResponse.getHeaders().get("X-RateLimit-Limit"));
// When
SUT.checkRepoPerUser(OWNER);
testScheduler.triggerActions();
// Then
then(viewMock).should().getOwner();
// Shut down the server. Instances cannot be reused.
mockWebServer.shutdown();
}
但是我得到了一个 ClassCastException:
java.lang.ClassCastException: io.reactivex.internal.operators.observable.ObservableJust cannot be cast to io.reactivex.functions.Consumer
是否有任何可行的方法来模拟 Response(List(Headers)),或者我应该开始考虑改变从端点获取数据的方式?
最佳答案
使用 retrofit 自己的 Response 对象创建模拟响应非常简单。正如您所说,构造函数是私有(private)的,但您可以使用静态方法创建成功的响应:
Response<List<Headers>> response = Response.success(headersList);
例如,如果您想创建一个因 401 错误而失败的响应,您可以这样做:
ResponseBody theBody = ResponseBody.create(MediaType.parse("text/html"), "");
Response<List<Headers>> response = Response.error(401, theBody);
关于android - 使用 retrofit2 和 rxjava2 对 android 应用程序进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54073274/
我正在进行 2 个 RX 调用,这些调用相互嵌套且相互依赖。服务器存在问题(由于各种原因目前无法解决),该问题在第二个嵌套调用中返回错误。 在这个问题得到解决之前,我需要确保如果第二次调用返回错误,则
这个问题在这里已经有了答案: How to resolve Duplicate files copied in APK META-INF/rxjava.properties (8 个答案) 关闭 5
我正在尝试将此 RxJava1 代码转换为 RxJava2 public static Observable listFolder(Path dir, String glob) { retur
这个问题在这里已经有了答案: RxJava 1 and RxJava 2 in the same project [duplicate] (1 个回答) How to resolve Duplica
有显示项目的RecyclerViewAdapter(该项目已经映射到数据库中)。 RecyclerViewAdapter 包含对 Presenter 的引用以加载项目。它还包含带有项目 ID 的 Ar
我想弄清楚如何在 Android 中使用 RxJava 将 Realm 对象保存在 Realm 中。到目前为止,结合所有这些的所有示例都是如何从 Realm 读取数据的。我想在 android 中使用
我在日志中收到此错误: Caused by java.lang.ClassCastException: java.net.UnknownHostException cannot be cast to
我有一个 API 服务类,其方法返回 Retrofit 提供的调用。 最近,Rx2Java 引入了 Single,所以我想将 Call 更改为 Single,但我不想更改逻辑。 例如 : 类接口(in
如何使用运算符让我始终获得以前和当前的值?如果可能的话,我想避免在管道外创建状态。 - time -> 1 2 3 4 | | | | Op
我正在努力实现以下目标。我加载了一个对象列表,我想获取稍后放入列表中的值。 首先,我使用 flatmap 将所有值收集到一个数组中(按山顺序),然后当一切完成后,我填充一个适配器。 我无法做的是每
是否可以选择使用 timeout 的变体不发射 Throwable ? 我要 complete事件发出。 最佳答案 您不需要使用 onErrorResumeNext 映射错误。您可以使用以下方法提供备
我们可以在 C# Rx 中异步执行一些代码,如下所示,使用 Observable.Start()。我想知道 RxJava 中的等价物是什么。 void Main() { AddTwoNum
问题:我有一个用户可以输入查询字符串的功能,我制作了 2 个可观察对象,一个用于查询我的本地数据库,另一个用于从 API 获取结果。这两个操作必须并行运行。我需要尽快显示来自数据库的结果,当 API
我正在尝试在 MVVM 中实现 ViewModel,将可观察对象作为“输入流”提供,将观察者作为“输出流”提供以供 View 绑定(bind)。 如果 getUser() 调用成功,下面的代码似乎可以
出于某种原因,我有时想使用 RxOperators 而不是普通的 java 方式来转换数据结构,因为它越来越干净。例如: Observable.from(listOfStrings) .filter(
我是 RxJava 新手,我需要以异步方式使用 Observable 功能。 我还需要使用超时:在我的示例中,我希望每个进程在 1 秒或更短的时间内结束。 这是我现在所做的: public stati
我正在尝试在网络请求期间在UI中显示进度条至少3秒钟。 此答案中描述的相同方法似乎不适用于Single。 RxJava Observable minimum execution time Single
我有一个可观察的(很热),它通过系统进程执行操作,并且我希望也运行一个间隔,直到该进程可观察达到 onComplete。 我看到区间运算符:http://reactivex.io/documentat
好吧,我是 RxJava2 的新手(嗯,我也不了解 RxJava),并且正在尝试使用 RxJava2 和 MVP 结构开发 Android 应用程序。 在该应用程序中,我正在对使用监听器的库进行异步调
如何将单个流拆分为单独的单个流,这样就可以执行以下操作而无需两次计算getUserId()? // getUserId() returns Single getUserId().flatMap { g
我是一名优秀的程序员,十分优秀!