参考文章:
搭建项目
以下demo来源于参考文章中。但是,与参考文章不同的是,参考文章采用的是Rxjava1,在本文,我们将采用Rxjava2,并且我会按照google官方给的MVP-TODO-MVP模式来写,所以跟参考文章可能会有所不同。
- build.gradle
implementation "io.reactivex.rxjava2:rxjava:2.1.11"
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.4.0'
implementation ('com.squareup.retrofit2:retrofit:2.4.0'){
exclude module: 'okhttp'
}
implementation "com.squareup.retrofit2:converter-gson:2.4.0"
implementation ("com.squareup.retrofit2:adapter-rxjava2:2.4.0"){
exclude module: 'rxjava'
}
- Model类:
User
public class User extends RealmObject {
@PrimaryKey
private int uid;
private String name;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof User) {
User user = (User) obj;
if (user.uid == uid){
return true;
}
}
return false;
}
}
- DataSource类:
UserDataSource
public interface UserDataSource {
/**
* 加载
* @param uid
* @return
*/
Flowable<User> loadUser(@Query("uid") int uid);
void saveUser(User user);
}
- 本地来源
LocalUserDataSource
/**
* 数据库缓存的用户信息
*/
public class LocalUserDataSource implements UserDataSource {
@Override
public Flowable<User> loadUser(int uid) {
return Flowable.create(emitter -> {
Realm realm = Realm.getDefaultInstance();
RealmResults<User> allAsync = realm.where(User.class)
.equalTo("uid",uid).findAll();
List<User> users = realm.copyFromRealm(allAsync);
if (users != null && users.size() > 0){
emitter.onNext(users.get(0));
emitter.onComplete();
}else {
emitter.onComplete();
}
}, BackpressureStrategy.BUFFER);
}
@Override
public void saveUser(User user) {
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
realm.copyToRealmOrUpdate(user);
realm.commitTransaction();
realm.close();
}
}
- 服务器来源
RemoteUserDataSource
public class RemoteUserDataSource implements UserDataSource {
private UserApi mUserApi;
/**
* 测试用
* @param userApi
*/
public RemoteUserDataSource(UserApi userApi){
this.mUserApi = userApi;
}
public RemoteUserDataSource(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.baidu.com/")
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
mUserApi = retrofit.create(UserApi.class);
}
private static OkHttpClient getOkHttpClient(){
return new OkHttpClient.Builder()
.build();
}
@Override
public Flowable<User> loadUser(int uid) {
return mUserApi.loadUser(uid).toFlowable(BackpressureStrategy.BUFFER);
}
@Override
public void saveUser(User user) {
//do nothing
}
}
- 数据源仓库
UserRepository
public class UserRepository implements UserDataSource{
private UserDataSource mRemote;
private UserDataSource mLocal;
public UserRepository(UserDataSource remote,UserDataSource local){
this.mRemote = remote;
this.mLocal = local;
}
@Override
public Flowable<User> loadUser(int uid) {
return Flowable.concat(mLocal.loadUser(uid),loadFromNet(uid))
.filter(user -> user != null)
.firstOrError()
.toFlowable();
}
private Flowable<User> loadFromNet(int uid){
return mRemote.loadUser(uid)
.doOnNext(user -> mLocal.saveUser(user));
}
@Override
public void saveUser(User user) {
mLocal.saveUser(user);
}
}
- View类以及Presenter协议类:
UserContract
public interface UserContract {
interface View{
void showUser();
}
interface Presenter{
void loadUser(int uid);
}
}
- presenter的实现类:
public class PresenterImpl implements UserContract.Presenter {
UserDataSource mUserRepository;
UserContract.View mUserView;
public PresenterImpl(UserDataSource userService , UserContract.View userView){
this.mUserRepository = userService;
this.mUserView = userView;
}
@Override
public void loadUser(int uid) {
mUserRepository.loadUser(uid)
.subscribe(new Consumer<User>() {
@Override
public void accept(User user) throws Exception {
mUserView.onUserLoaded(user);
}
});
}
}
基本项目搭建完成了,接下来可以愉快的玩耍了。
引入测试库
在项目中引入测试中所需要用到的库:
testImplementation 'junit:junit:4.12'
testImplementation "org.robolectric:robolectric:3.3.2"
testImplementation 'org.mockito:mockito-core:2.16.0'
testImplementation "org.robolectric:robolectric:3.3.2"
testImplementation 'org.robolectric:shadows-support-v4:3.4-rc2'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.9.1'
androidTestImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.0'
androidTestImplementation 'com.android.support.test:rules:1.0.0'
如果要使用context,记得 还要引入:
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
位置如图:
不然会报错:
Android IllegalStateException No instrumentation registered! Must run under a registering instrumentation
先看一下各个类在项目中的位置:
presenter层的测试
public class PresenterImplTest extends TestCase {
@Mock
UserContract.View mUserView;
@Mock
UserDataSource mUserRepository;
UserContract.Presenter mUserPresenter;
@Before
public void setUp() throws Exception{
MockitoAnnotations.initMocks(this);
// mUserView = mock(UserContract.View.class);
// mUserRepository = mock(UserDataSource.class);
mUserPresenter = new PresenterImpl(mUserRepository,mUserView);
}
@Test
public void testLoadUser() throws Exception{
User user = new User();
user.setUid(1);
user.setName("kkmike999");
//如果调用了mUserService的loadUser方法,那么返回Observable.just(user)
when(mUserRepository.loadUser(anyInt()))
.thenReturn(Flowable.just(user));
//调用loadUser方法
mUserPresenter.loadUser(1);
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(mUserRepository).loadUser(1);
verify(mUserView).onUserLoaded(captor.capture());
User result = captor.getValue();
Assert.assertEquals(result.getUid(),1);
// 返回参数是否符合预期 Assert.assertEquals(result.getName(),"kkmike999");
}
}
ArgumentCaptor介绍
- 使用场景:在某些场景中,不光要对方法的返回值和调用进行验证,同时需要验证一系列逻辑之后所传入方法的参数是否符合期望。此时,我们就可以使用ArgumentCaptor。
- 使用:通过ArgumentCaptor对象的forClass(Class clazz)方法来构建ArgumentCaptor对象。然后便可在验证时对方法的参数进行捕获,最后验证捕获的参数值。如果方法有多个参数都要捕获验证,那就需要创建多个ArgumentCaptor对象处理。
- ArgumentCaptor的Api :
- argument.capture() 捕获方法参数
- argument.getValue() 获取方法参数值,如果方法进行了多次调用,它将返回最后一个参数值
- argument.getAllValues() 方法进行多次调用后,返回多个参数值
参考博客:学习Mocktio
UserRepository的测试
public class UserRepositoryTest extends TestCase {
@Mock
UserDataSource mRemote;
@Mock
UserDataSource mLocal;
UserRepository mRepository;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
mRepository = new UserRepository(mRemote,mLocal);
}
@Test
public void testLoadFromLocalSuccess(){
int uid = 1;
User user = new User();
user.setUid(uid);
user.setName("kkmike999");
when(mLocal.loadUser(uid)).thenReturn(Flowable.just(user));
when(mRemote.loadUser(uid)).thenReturn(Flowable.error(new Throwable("")));
TestSubscriber<User> testObserver = new TestSubscriber<>();
mRepository.loadUser(uid).subscribe(testObserver);
//判断返回的User跟 user是一样的
testObserver.assertValue(user);
}
}
LocalDataSource的测试
@RunWith(AndroidJUnit4.class)
@LargeTest
public class LocalUserDataSourceTest extends TestCase {
Context mContext;
LocalUserDataSource localUserDataSource ;
@Before
public void setUp(){
mContext = InstrumentationRegistry.getTargetContext();
Realm.init(mContext);
localUserDataSource = new LocalUserDataSource();
}
@Test
public void testSave_retrievedUser(){
User user = new User();
user.setUid(1);
user.setName("kkmike999");
localUserDataSource.saveUser(user);
//get
TestSubscriber<User> subscriber = new TestSubscriber<>();
localUserDataSource.loadUser(1).subscribe(subscriber);
subscriber.assertValue(user);
}
}
RemoteUserDataSource测试
/**
* 作用:把异步变成同步
*/
public class RxjavaTestRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
RxAndroidPlugins.reset();
RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
base.evaluate();
}
};
}
}
RxjavaTestRule是在test文件夹下的。
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class RemoteUserDataSourceTest extends TestCase {
private MockWebServer server;
@Rule
public RxjavaTestRule rule = new RxjavaTestRule();
private RemoteUserDataSource mRemoteUserDataSource;
@Before
public void setUp() {
ShadowLog.stream = System.out;
// 创建一个 MockWebServer
server = new MockWebServer();
//设置响应,默认返回http code是 200
MockResponse mockResponse = new MockResponse()
.addHeader("Content-Type", "application/json;charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody("{\\n\" +\n" +
" \" \\\"name\\\": \\\"kkmex\\\",\\n\" +\n" +
" \" \\\"uid\\\": 12\\n\" +\n" +
" \"}\"");
// MockResponse mockResponse1 = new MockResponse()
// .addHeader("Content-Type", "application/json;charset=utf-8")
// .setResponseCode(404)
// .throttleBody(5, 1, TimeUnit.SECONDS) //一秒传递5个字节,模拟网速慢的情况
// .setBody("{\"error\": \"网络异常\"}");
server.enqueue(mockResponse); //成功响应
// server.enqueue(mockResponse1);//失败响应
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://" + server.getHostName() + ":" + server.getPort() + "/") //设置对应的Host与端口号
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
UserApi userApi = retrofit.create(UserApi.class);
mRemoteUserDataSource = new RemoteUserDataSource(userApi);
}
@Test
public void getUserTest() throws Exception {
//请求不变
mRemoteUserDataSource.loadUser(1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<User>() {
@Override
public void onSubscribe(Subscription s) {
}
@Override
public void onNext(User user) {
assertEquals("kkmex", user.getName());
assertEquals(12, user.getUid());
}
@Override
public void onError(Throwable e) {
Log.e("Test", e.toString());
}
@Override
public void onComplete() {
}
});
//验证我们的请求客户端是否按预期生成了请求
RecordedRequest request = server.takeRequest();
assertEquals("GET /getUser?uid=1 HTTP/1.1", request.getRequestLine());
assertEquals("okhttp/3.10.0", request.getHeader("User-Agent"));
// 关闭服务
server.shutdown();
}
}
参考文章: Android单元测试(五):网络接口测试
好了,接下里就剩下View部分的单元测试了。这部分只能先占坑了~~~
提醒:上面使用
Flowable是不太合适的,一般情况下,对于接收和处理速度差不多的场景,用Observable就可以了。对于从网络获取数据这种,一般一次就返回了所有数据,使用Maybe就可以了,没有必要使用Flowable。
其他
在使用Rxjava2进行测试时,有下面一些技巧:
- 当返回
Flowable,采用TestSubscriber;当返回Observable时,采用TestObserver; TestObserver如何取到OnNext中的值?答:values()方法
TestObserver<List<User>> testObserver = new TestObserver<>();
testObserver.values();