本实践案例除了学习单元测试外,还在学习实践TDD,Retrofit,RxJava,MVP。最近也在学习摸索,觉得有必要做一个总结记录和分享,因为参考了很多大神分享的文章,获益良多。
项目github地址
单元测试的时候,我们只验证平级的交互流程,即某个方法的功能,他调用的其他方法的细节不做验证,只需要验证,他调用了某个方法,某个方法给予结果,他收到了,就行
首先考虑登录的业务交互流程逻辑,测试LoginPresenter
校验数据的有效性-->等待弹框-->网络验证 -->关闭弹框-->结果提示
public class LoginPresenterTest {
@Mock
LoginContract.IView loginView;
@Mock
LoginBiz loginBiz;
private LoginPresenter loginPresenter;
ArgumentCaptor loginCallbackArgumentCaptor;
/**
* 初始化对象
*/
@Before
public void setupLoginPresenter() {
MockitoAnnotations.initMocks(this);
loginCallbackArgumentCaptor = ArgumentCaptor.forClass(OnLoginCallback.class);
loginPresenter = new LoginPresenter(loginView, loginBiz);
}
/**
* 登录操作
* 弹框等待--登录--取消弹框--跳转页面
*/
@Test
public void testLoginPresenterSuccess() {
String name = "admin";
String pw = "12345678";
loginPresenter.login(name, pw);
//显示进度框
verify(loginView).setLoginIndicator(true);
//调用了登录业务
verify(loginBiz).login(eq(name), eq(pw), loginCallbackArgumentCaptor.capture());
//调用onSuccess 回调
loginCallbackArgumentCaptor.getValue().onSuccess(new UserData());
//弹框取消了
verify(loginView).setLoginIndicator(false);
verify(loginView).jumpToMainActivity();
}
/**
* 弹框等待-登录-取消弹框--提示错误
*/
@Test
public void testLoginPresenterFailed() {
String name = "admin";
String pw = "12345678";
loginPresenter.login(name, pw);
//显示进度框
verify(loginView).setLoginIndicator(true);
//调用了登录业务
verify(loginBiz).login(eq(name), eq(pw), loginCallbackArgumentCaptor.capture());
loginCallbackArgumentCaptor.getValue().onFailed("用户名或者密码不对");
//弹框取消了
verify(loginView).setLoginIndicator(false);
verify(loginView).showLoginFailed("用户名或者密码不对");
}
/**
* 检验登录输入参数
*/
@Test
public void testLoginParamsInvalid() {
String errEmptyName = "输入的用户名不能为空";
String errPWName = "输入的密码不能为空";
String errPwLength = "请输入8位密码";
loginPresenter.login("", "");
verify(loginView).showErrorParams(errEmptyName);
//没有其他的交互被调用了,防止条件校验失败未return
verifyNoMoreInteractions(loginView);
verify(loginView, never()).setLoginIndicator(anyBoolean());
//密码不能为空
loginPresenter.login("admin", "");
verify(loginView).showErrorParams(errPWName);
verify(loginView, never()).setLoginIndicator(anyBoolean());
//密码长度不对
loginPresenter.login("admin", "1234567");
verify(loginView).showErrorParams(errPwLength);
verify(loginView, never()).setLoginIndicator(anyBoolean());
}
}
上面是LoginPresenter的交互逻辑,需要验证它与上下游的交互流程是否正确,已经覆盖到。验证了对LoginBiz的调用已经对LoginView的相关调用。
public class LoginPresenter implements LoginContract.IPresenter{
private LoginContract.IView loginView;
private LoginBiz loginBiz;
@Inject
public LoginPresenter(LoginContract.IView loginView, LoginBiz loginBiz) {
this.loginView = checkNotNull(loginView," loginView can't be null");
this.loginBiz = checkNotNull(loginBiz,"loginBiz can't be null");
}
public void login(String name, String pw) {
if (!verfiryParams(name, pw))return;
loginView.setLoginIndicator(true);
loginBiz.login(name, pw, new OnLoginCallback(){
@Override
public void onSuccess(UserData userData) {
loginView.setLoginIndicator(false);
loginView.jumpToMainActivity();
}
@Override
public void onFailed(String errMsg) {
loginView.setLoginIndicator(false);
loginView.showLoginFailed(errMsg);
}
});
}
private boolean verfiryParams(String name, String pw) {
if (name==null||"".equals(name)) {
loginView.showErrorParams("输入的用户名不能为空");
return false;
}
if (pw == null || "".equals(pw)) {
loginView.showErrorParams("输入的密码不能为空");
return false;
}
/*if (pw.length() != 8) {
loginView.showErrorParams("请输入8位密码");
return false;
}*/
return true;
}
}
可以看出来,在验证LoginPresenter的login方法时,我只验证了他对loginView的调用,以及对loginBiz的调用,至于loginView.setLoginIndicator 和loginBiz.login方法具体有没有实现执行的是什么,在这个交互里面我是不关心的。
PS:在testLoginParamsInvalid中 有一行代码verifyNoMoreInteractions(loginView),因为我在LoginPresenter 的login中verfiryParams无效后,忘了return,导致后面的交互也在走,是测试没有覆盖到的,后面运行的时候,才发现这个问题,补加的一句。
所以可见测试的重要性,已经自己要充分的理解交互的流程与关键点。
测试LoginBiz
public class LoginBiz {
public void login(String name, String pw, final OnLoginCallback loginCallback) {
//刚开始是这样写的,但是这样写 1.不方便测试,2.也破环方法的平行层级结构即步骤,他的步骤,一获取call对象,二执行。
// HashMap hashMap = new HashMap();
// hashMap.put("loginId", name);
// hashMap.put("password", pw);
// Call login= RetrofitBuilder.getHttpService().login(hashMap);
Call login = getLoginCall(name,pw);
login.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
loginCallback.onSuccess(new UserData());
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
}
Call getLoginCall(String name, String pw) {
HashMap hashMap = new HashMap();
hashMap.put("loginId", name);
hashMap.put("password", pw);
return RetrofitBuilder.getHttpService().login(hashMap);
}
}
逻辑很简单,可以看下注释的地方,提炼一个getLoginCall方法后,方面的层级步骤清楚简单了,但是感觉有点破坏了封装性,但是这样有利于测试mock,不然很难下手测试。
因为我们要测试LoginBiz 的login方法,主要是验证其与OnLoginCallback 的交互是预期的正确的,也即onResponse和onFailure后,有对应调用OnLoginCallback 的方法,那么至于这个login.enqueue()方法的执行我们不关心也不会去等(这个可是网络请求,这一块的验证不在范围之内)
看下面测试的实现:一个是通过重写 来返回mock对象,一个是通过spy返回mock对象。同时也用两种方式实现了对结果的回调,一个是ArgumentCaptor,一个是doAnswer。
@Test
public void testLogin2(){
final Call mock = mock(Call.class);
//创建对象是,重写方法直接返回mock对象验证
LoginBiz loginBiz = new LoginBiz(){
@Override
protected Call getLoginCall(String name, String pw) {
return mock;
}
};
String name = "adimin";
String pw = "admin";
OnLoginCallback onLoginCallback = mock(OnLoginCallback.class);
//调用login方法
loginBiz.login(name, pw, onLoginCallback);
ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Callback.class);
//验证enqueue方法被调用,并捕获其参数
verify(mock).enqueue(argumentCaptor.capture());
Call call=null;
Response response = null;
//回调enqueue方法参数的onResponse方法(跳过真实的异步网络请求)
argumentCaptor.getValue().onResponse(call,response);
//验证onLoginCallback的方法有被调用
verify(onLoginCallback).onSuccess(any(UserData.class));
}
@Test
public void testLogin3(){
LoginBiz spy = spy(LoginBiz.class);
String name = "adimin";
String pw = "admin";
OnLoginCallback onLoginCallback = mock(OnLoginCallback.class);
//主要是mock Call对象
final Call mock = mock(Call.class);
doReturn(mock).when(spy).getLoginCall(anyString(), anyString());
//spy会执行真实的login方法,而login中getLoginCall时,会返回上面预设的Call的mock对象
//如果此处是LoginBiz的mock对象,那么login的真实方法是不会被执行的
spy.login(name, pw, onLoginCallback);
ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Callback.class);
verify(mock).enqueue(argumentCaptor.capture());
Call call=null;
Response response = null;
argumentCaptor.getValue().onResponse(call,response);
verify(onLoginCallback).onSuccess(any(UserData.class));
}
@Test
public void testLogin4(){
LoginBiz loginBiz = new LoginBiz();
LoginBiz spy = spy(loginBiz);
final Call mock = mock(Call.class);
doReturn(mock).when(spy).getLoginCall(anyString(), anyString());
String name = "adimin";
String pw = "admin";
OnLoginCallback onLoginCallback = mock(OnLoginCallback.class);
//通过doAnswer 来默认回调onResponse方法
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] arguments = invocation.getArguments();
Callback callback = (Callback) arguments[0];
callback.onResponse(null,null);
return null;
}
}).when(mock).enqueue(any(Callback.class));
spy.login(name, pw, onLoginCallback);
verify(onLoginCallback).onSuccess(any(UserData.class));
}
引入Dagger依赖注入,更好的改造LoginBiz与测试
public class LoginBiz {
HttpSerivce httpSerivce;
@Inject
public LoginBiz(HttpSerivce httpSerivce) {
this.httpSerivce = httpSerivce;
}
public void login(String name, String pw, final OnLoginCallback loginCallback) {
Call login = getLoginCall(name,pw);
login.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
loginCallback.onSuccess(new UserData());
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
}
private Call getLoginCall(String name, String pw) {
HashMap hashMap = new HashMap();
hashMap.put("loginId", name);
hashMap.put("password", pw);
return httpSerivce.login(hashMap);
}
}
public class TestLoginBiz {
@Test
public void testLogin(){
HttpSerivce httpSerivce = mock(HttpSerivce.class);
LoginBiz loginBiz = new LoginBiz(httpSerivce);
String name = "adimin";
String pw = "admin";
OnLoginCallback onLoginCallback = mock(OnLoginCallback.class);
//主要是mock Call对象
final Call mockCall = mock(Call.class);
//当mock对象的login方法执行时,将返回替换为mock对象
doReturn(mockCall).when(httpSerivce).login(any(HashMap.class));
loginBiz.login(name, pw, onLoginCallback);
ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Callback.class);
verify(mockCall).enqueue(argumentCaptor.capture());
Call call=null;
Response response = null;
argumentCaptor.getValue().onResponse(call,response);
verify(onLoginCallback).onSuccess(any(UserData.class));
}
}
这样可以有更好的封装性与更简洁的测试代码,不用为了mock Call对象,而暴露getLoginCall方法给外部mock,他本应该是私有的。而且测试的代码也很简单流畅,只要mock就行,不用spy,或者重写。
或者代码调整成这样的,更能看出优势和符合平时书写的习惯
public class LoginBiz {
HttpSerivce httpSerivce;
@Inject
public LoginBiz(HttpSerivce httpSerivce) {
this.httpSerivce = httpSerivce;
}
public void login(String name, String pw, final OnLoginCallback loginCallback) {
HashMap hashMap = new HashMap();
hashMap.put("loginId", name);
hashMap.put("password", pw);
Call login = httpSerivce.login(hashMap);
login.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
loginCallback.onSuccess(new UserData());
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
}
但是还是推荐上面的方式,让login方法的步骤层级简单明晰些。
LoginActivity验证
1.验证了Activity实例化后,loginPresenter 对象也被实例化了
2.验证了登录按钮与loginPresenter 的交互正确
3.验证了对Progerss的调用正确的显示和关闭了
4.验证toast的消息正确的显示了。
这里可能会有疑惑,为何这里是分开验证的。不是应该验证点击登录后,依次验证loginPresenter 的login方法被调用了,setLoginIndicator(true)被调用了,然后setLoginIndicator(false)被调用了么?
如果这样验证,你会发现很难下手,由于loginPresenter是被mock了的,其login的真实方法不会被调用,所以setLoginIndicator的调用无法被验证到。当然你会说可以用spy对象,但是,这样你有得考虑网络请求的等待,是不是想屎了。
之所以会陷入这样的困局,主要是对单元测试的理解有误,一是粒度的把握,二是职责的确定。刚才那样的测试应该属于继承测试,而不是单元测试。
单元测试应该主要验证当前类
- 调用其他方法,即需求其他的类提供服务时,他是否准确的触发了,如登录时对loginPresenter的login方法的调用,而其真正的执行我不关心,也无需知道,不在我的职责范畴之类
- 被其他类调用,即对外提供服务时,自己是否正确的响应并执行了。
这两点,也就是我们类的边界,这样单元测试就会轻松很多。回到前面说的对setLoginIndicator(true)和setLoginIndicator(false)的验证,这应该是在测试loginPresenter 时,其需要保证的,而不在Acitivty中,Acitivty只要知道,点击登录时,我的请求发出去了,就可以了,我告诉你了,你不执行,我就很无奈了;我给你提供了弹框方法,而且我自检可以弹出,可是如果你不告诉我,不请求我,我也只能很无辜了。我做好了我该做的。
回过头再来看mock的设计艺术,你会发现他就是让你关注当前类的功能与交互,与之相关的类,直接用mock来验证,而不真实的去执行,因为当前类,只关心到这个层面。
@RunWith(RobolectricTestRunner.class) @Config(constants =BuildConfig.class,sdk =23)
public class LoginActivityTest {
LoginActivity mainActivity;
@Before
public void stetUp(){
mainActivity = Robolectric.setupActivity(LoginActivity.class);
//验证loginPresenter对象不能为null
assertThat("loginPresenter can't be null",mainActivity.loginPresenter,notNullValue());
}
@Test
public void testLoginBtn(){
LoginPresenter loginPresenter = mock(LoginPresenter.class);
//替换mock对象,方面后面验证交互
mainActivity.loginPresenter = loginPresenter;
Button btn_login = findView(mainActivity, R.id.btn_login);
btn_login.performClick();
//验证loginPresenter的方法被调用了
verify(loginPresenter).login(anyString(), anyString());
}
@Test
public void testShowDialog(){
mainActivity.setLoginIndicator(true);
AlertDialog latestAlertDialog = ShadowProgressDialog.getLatestAlertDialog();
assertThat(latestAlertDialog.isShowing(), is(true));
mainActivity.setLoginIndicator(false);
assertFalse(latestAlertDialog.isShowing());
}
@Test
public void testShowErrorMsg() {
String errMsg = "用户名或者密码不能为空";
mainActivity.showErrorParams(errMsg);
Assert.assertThat(ShadowToast.getTextOfLatestToast(),equalTo(errMsg));
}
private T findView(Activity parentView, int idRes) {
return (T)parentView.findViewById(idRes);
}
}
LoginActivity的代码
public class LoginActivity extends AppCompatActivity implements LoginContract.IView {
private Button btn_login;
private EditText et_name;
private EditText et_password;
private ProgressDialog progressDialog;
@Inject LoginPresenter loginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注入Presenter对象
DaggerLoginCompent.builder().loginPresenterModule(new LoginPresenterModule(this)).build().inject(this);
progressDialog = new ProgressDialog(this);
btn_login = findView(R.id.btn_login);
et_name = findView(R.id.ed_name);
et_password = findView(R.id.ed_password);
btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
loginPresenter.login(et_name.getText().toString(), et_password.getText().toString());
}
});
}
protected T findView(int idRes) {
return (T) findViewById(idRes);
}
protected void toast(String msg) {
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
}
@Override
public void setLoginIndicator(boolean b) {
if (b) {
progressDialog.show();
} else {
progressDialog.dismiss();
}
}
@Override
public void showErrorParams(String errEmptyName) {
toast(errEmptyName);
}
@Override
public void jumpToMainActivity() {
}
@Override
public void showLoginFailed(String errMsg) {
}
}
用Retroft进行网络请求并验证
由于网络请求的异步性,这里测试异步回调有3中方式
1.将发起的异步请求用同步请求去代替,即将enqueue方法,替换为execute
2.用拦截器,将异步请求变为同步请求
3.测试线程沦陷等待异步线程结果返回
/**
* 同步测试API
*/
@Test
@Config(constants = com.tospur.exmind.study_tdd.testNet.BuildConfig.class,sdk =23)
public void testLoginSync(){
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
Call login = httpSerivce.login(hashMap);
Log.i("TestHttpService", "===begin===");
System.out.println("===begin===");
try {
Response responseBody=login.execute();
System.out.println(responseBody.toString());
} catch (IOException e) {
e.printStackTrace();
}
Log.i("TestHttpService", "===end===");
System.out.println("===end===");
}
@Test
@Config(constants = com.tospur.exmind.study_tdd.testNet.BuildConfig.class,sdk =23)
public void testLoginAsyncToSync (){
HttpSerivce httpSerivce = new Retrofit.Builder().baseUrl("http://172.18.84.243:8080/agent_cloud/")
.addConverterFactory(GsonConverterFactory.create())
.client(new OkHttpClient.Builder()
.addInterceptor(headInterceptor)
.addInterceptor(loggingInterceptor)
.dispatcher(dispatcher)
.build())
.build().create(HttpSerivce.class);
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
Call> login = httpSerivce.loginUser(hashMap);
login.enqueue(new Callback>() {
@Override
public void onResponse(Call> call, Response> response) {
System.out.println("==onResponse=="+response.body());
}
@Override
public void onFailure(Call> call, Throwable t) {
}
});
System.out.println("==end==");
}
Dispatcher dispatcher = new Dispatcher(new AbstractExecutorService() {
private boolean shutingDown = false;
private boolean terminated = false;
@Override
public void shutdown() {
this.shutingDown = true;
this.terminated = true;
}
@NonNull
@Override
public List shutdownNow() {
return new ArrayList<>();
}
@Override
public boolean isShutdown() {
return this.shutingDown;
}
@Override
public boolean isTerminated() {
return this.terminated;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void execute(Runnable command) {
command.run();
}
});
@Test
@Config(constants = com.tospur.exmind.study_tdd.testNet.BuildConfig.class,sdk =23)
public void testLoginAsync() throws InterruptedException {
HttpSerivce httpSerivce = new Retrofit.Builder().baseUrl("http://172.18.84.243:8080/agent_cloud/")
.addConverterFactory(GsonConverterFactory.create())
.client(new OkHttpClient.Builder()
.addInterceptor(headInterceptor)
.addInterceptor(loggingInterceptor)
.build())
.build().create(HttpSerivce.class);
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
final AtomicBoolean waitLock = new AtomicBoolean(false);
Call> login = httpSerivce.loginUser(hashMap);
login.enqueue(new Callback>() {
@Override
public void onResponse(Call> call, Response> response) {
System.out.println("==onResponse=="+response.body());
waitLock.set(true);
}
@Override
public void onFailure(Call> call, Throwable t) {
}
});
System.out.println("==end=11==");
while (!waitLock.get()) {
Thread.sleep(1000);
ShadowLooper.runUiThreadTasks();
}
System.out.println("==end=22==");
}
Retrofit+Rxjava
@RunWith(RobolectricTestRunner.class) @Config(constants = com.tospur.exmind.study_tdd.testNet.BuildConfig.class,sdk =23)
public class TestRetrofitWithRx {
@Test
public void testLogin(){
HttpSerivce httpSerivce = new Retrofit.Builder().baseUrl("http://172.18.84.243:8080/agent_cloud/")
.addConverterFactory(GsonConverterFactory.create())
.client(new OkHttpClient.Builder().addInterceptor(headInterceptor).addInterceptor(loggingInterceptor).build())
.build().create(HttpSerivce.class);
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
Call> login = httpSerivce.loginUser(hashMap);
try {
Response> execute = login.execute();
System.out.println(execute.body().getMsg());
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testLoginWithRx(){
HttpSerivce httpSerivce = new Retrofit.Builder().baseUrl("http://172.18.84.243:8080/agent_cloud/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(new OkHttpClient.Builder().addInterceptor(headInterceptor).addInterceptor(loggingInterceptor).build())
.build().create(HttpSerivce.class);
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
Observable> observable = httpSerivce.loginObservable(hashMap);
observable.subscribe( new Observer>(){
@Override
public void onSubscribe(Disposable d) {
System.out.println("TestRetrofitWithRx.onSubscribe");
}
@Override
public void onNext(NetResult value) {
System.out.println("TestRetrofitWithRx.onNext");
}
@Override
public void onError(Throwable e) {
System.out.println("TestRetrofitWithRx.onError");
}
@Override
public void onComplete() {
System.out.println("TestRetrofitWithRx.onComplete");
}
});
}
@Test
public void testLoginWithRxFlowable(){
HttpSerivce httpSerivce = new Retrofit.Builder().baseUrl("http://172.18.84.243:8080/agent_cloud/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(new OkHttpClient.Builder().addInterceptor(headInterceptor).addInterceptor(loggingInterceptor).build())
.build().create(HttpSerivce.class);
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
Flowable> netResultFlowable = httpSerivce.loginFlowable(hashMap);
netResultFlowable.subscribe(new Subscriber>() {
@Override
public void onSubscribe(Subscription s) {
System.out.println("TestRetrofitWithRx.onSubscribe");
s.request(1);
}
@Override
public void onNext(NetResult userNetResult) {
System.out.println("TestRetrofitWithRx.onNext "+userNetResult.getData().toString());
}
@Override
public void onError(Throwable t) {
System.out.println("TestRetrofitWithRx.onError");
}
@Override
public void onComplete() {
System.out.println("TestRetrofitWithRx.onComplete");
}
});
}
@Test
public void testLoginWithRxFlowableAndMap(){
HttpSerivce httpSerivce = new Retrofit.Builder().baseUrl("http://172.18.84.243:8080/agent_cloud/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(new OkHttpClient.Builder().addInterceptor(headInterceptor).addInterceptor(loggingInterceptor).build())
.build().create(HttpSerivce.class);
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
Flowable> netResultFlowable = httpSerivce.loginFlowable(hashMap);
netResultFlowable.map(new Function, User>() {
@Override
public User apply(NetResult userNetResult) throws Exception {
System.out.println("TestRetrofitWithRx.apply");
return userNetResult.getData();
}
}).subscribe(new Subscriber() {
@Override
public void onSubscribe(Subscription s) {
System.out.println("TestRetrofitWithRx.onSubscribe");
s.request(1);
}
@Override
public void onNext(User user) {
System.out.println(user.toString());
}
@Override
public void onError(Throwable t) {
System.out.println("TestRetrofitWithRx.onError");
}
@Override
public void onComplete() {
System.out.println("TestRetrofitWithRx.onComplete");
}
});
}
@Test
public void testLoginWithRxFlowableAndMapProgressBar(){
HttpSerivce httpSerivce = new Retrofit.Builder().baseUrl("http://172.18.84.243:8080/agent_cloud/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(new OkHttpClient.Builder().addInterceptor(headInterceptor).addInterceptor(loggingInterceptor).build())
.build().create(HttpSerivce.class);
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
Flowable> netResultFlowable = httpSerivce.loginFlowable(hashMap);
netResultFlowable.map(new Function, User>() {
@Override
public User apply(NetResult userNetResult) throws Exception {
return userNetResult.getData();
}
}).subscribe(new Subscriber() {
@Override
public void onSubscribe(Subscription s) {
System.out.println("TestRetrofitWithRx.onSubscribe");
s.request(1);
}
@Override
public void onNext(User user) {
System.out.println(user.toString());
}
@Override
public void onError(Throwable t) {
System.out.println("TestRetrofitWithRx.onError");
}
@Override
public void onComplete() {
System.out.println("TestRetrofitWithRx.onComplete");
}
});
}
@Test
public void testLoginWithRxFlowableAndMapException(){
HttpSerivce httpSerivce = new Retrofit.Builder().baseUrl("http://172.18.84.243:8080/agent_cloud/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(new OkHttpClient.Builder().addInterceptor(headInterceptor).addInterceptor(loggingInterceptor).build())
.build().create(HttpSerivce.class);
final HashMap hashMap = new HashMap();
hashMap.put("loginId", "21048");
hashMap.put("password", "12050646");
Flowable> netResultFlowable = httpSerivce.loginFlowable(hashMap);
netResultFlowable.map(new Function, User>() {
@Override
public User apply(NetResult userNetResult) throws Exception {
// if (userNetResult.getCode() == 0) {
// return userNetResult.getData();
// }else{
throw new ApiException(userNetResult.getCode(), userNetResult.getMsg());
// }
}
}).subscribe(new Subscriber() {
@Override
public void onSubscribe(Subscription s) {
System.out.println("TestRetrofitWithRx.onSubscribe");
s.request(1);
}
@Override
public void onNext(User user) {
System.out.println(user.toString());
}
@Override
public void onError(Throwable t) {
System.out.println("TestRetrofitWithRx.onError");
if (t instanceof ApiException) {
}
}
@Override
public void onComplete() {
System.out.println("TestRetrofitWithRx.onComplete");
}
});
}
Interceptor headInterceptor = new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("X-Token", "")
.header("X-appOS", "android")
.header("X-version", BuildConfig.VERSION_NAME)
.header("X-CaseId", "")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
};
Interceptor loggingInterceptor = new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
final long t1 = System.nanoTime();
System.out.println(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()));
okhttp3.Response response = chain.proceed(request);
final long t2 = System.nanoTime();
final String responseBody = response.body().string();
System.out.println(String.format("Received response for %s in %.1fms%n%s%s", response.request().url(), (t2 - t1) / 1e6d, response.headers(), responseBody));
return response.newBuilder()
.body(ResponseBody.create(response.body().contentType(), responseBody))
.build();
}
};
}
小结
善用get将不容易mock出的对象暴露出来,方便mock。如上面的getLoginCall()方法
ArgumentCaptor可以捕获参数,更优雅的实现doAnswer式的回调效果.
mock和spy的区别,mock不会执行方法体,而spy会对方法进行真实的调用。而spy的适用场景,就如上面测试loginBiz中的一个案例。我们需要真实的执行其方法,检查其交互流程,但又对依赖到的方法或者对象进行mock,以改变其行为,这个时候spy就能很好的施展了。
另外一定,使用的对象不同,mock主要是针对当前对象使用到的类,验证与其的交互;而spy主要针对当前对象,要改变当前对象部分方法的预期。
如下面示例: spy就改变了当前测试对象LoginBiz 自己的方法getLoginCall的返回预期,返回一个要使用到的类的mock对象。
@Test
public void testLogin3(){
LoginBiz spy = spy(LoginBiz.class);
String name = "adimin";
String pw = "admin";
OnLoginCallback onLoginCallback = mock(OnLoginCallback.class);
//主要是mock Call对象
final Call mock = mock(Call.class);
doReturn(mock).when(spy).getLoginCall(anyString(), anyString());
//spy会执行真实的login方法,而login中getLoginCall时,会返回上面预设的Call的mock对象
//如果此处是LoginBiz的mock对象,那么login的真实方法是不会被执行的
spy.login(name, pw, onLoginCallback);
ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Callback.class);
verify(mock).enqueue(argumentCaptor.capture());
Call call=null;
Response response = null;
argumentCaptor.getValue().onResponse(call,response);
verify(onLoginCallback).onSuccess(any(UserData.class));
}
public class LoginBiz {
HttpSerivce httpSerivce;
@Inject
public LoginBiz(HttpSerivce httpSerivce) {
this.httpSerivce = httpSerivce;
}
public void login(String name, String pw, final OnLoginCallback loginCallback) {
//刚开始是这样写的,但是这样写 1.不方便测试,2.也破环方法的平行层级结构即步骤,他的步骤,一获取call对象,二执行。
// HashMap hashMap = new HashMap();
// hashMap.put("loginId", name);
// hashMap.put("password", pw);
// Call login= RetrofitBuilder.getHttpService().login(hashMap);
Call login = getLoginCall(name,pw);
login.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
loginCallback.onSuccess(new UserData());
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
}
Call getLoginCall(String name, String pw) {
HashMap hashMap = new HashMap();
hashMap.put("loginId", name);
hashMap.put("password", pw);
return httpSerivce.login(hashMap);
}
}