Updating gradle to 2.3 .

Updating RXjava to version 2 .
Adding unit test for home presenter .
This commit is contained in:
Ahmed Eltaher
2017-03-12 15:28:21 +01:00
parent 481c04ca01
commit b0fbce7a4e
15 changed files with 239 additions and 51 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.iml
.gradle
/.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
@@ -7,3 +8,4 @@
/build
/captures
.externalNativeBuild
/.idea/

View File

@@ -1,6 +1,5 @@
apply plugin: 'com.android.application'
//Dagger
apply plugin: 'com.neenbedankt.android-apt'
//ButterKnife
apply plugin: 'com.jakewharton.butterknife'
//lamda
@@ -59,10 +58,10 @@ dependencies {
compile "com.android.support:design:${supportLibraryVersion}"
//Dagger
compile "com.google.dagger:dagger:${daggerVersion}"
apt "com.google.dagger:dagger-compiler:${daggerVersion}"
annotationProcessor "com.google.dagger:dagger-compiler:${daggerVersion}"
//Butter knife
compile "com.jakewharton:butterknife:${butterKnifeVersion}"
apt "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"
annotationProcessor "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"
//event bus
compile 'com.squareup:otto:1.3.8'
//Logging
@@ -81,9 +80,9 @@ dependencies {
compile "com.hannesdorfmann.parcelableplease:annotation:${parcelablepleaseVersion}"
compile "com.hannesdorfmann.parcelableplease:processor:${parcelablepleaseVersion}"
//Android RX
compile 'io.reactivex:rxandroid:1.2.1'
compile 'io.reactivex:rxjava:1.1.6'
compile "com.squareup.retrofit2:adapter-rxjava:${retrofitVersion}"
compile 'io.reactivex.rxjava2:rxjava:2.0.7'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
// espresso , junit for testing
testCompile 'junit:junit:4.12'
compile('com.android.support.test:rules:0.5') {
@@ -115,4 +114,7 @@ dependencies {
exclude group: "javax.inject", module: "javax.inject"
exclude group: 'com.android.support', module: 'support-annotations'
}
//Mockito
testCompile "org.mockito:mockito-core:${mockitoVersion}"
androidTestCompile "org.mockito:mockito-core:${mockitoVersion}"
}

View File

@@ -9,12 +9,13 @@ ext {
targetSdkVersion = 24
//Libraries
buildToolsVersion = '24.0.1'
supportLibraryVersion = '24.2.1'
buildToolsVersion = '25.0.2'
supportLibraryVersion = '25.1.1'
daggerVersion = '2.0.2'
parcelablepleaseVersion = '1.0.2'
espressoVersion = '2.2.2'
retrofitVersion = '2.2.0'
okhttpVersion = '3.3.0'
butterKnifeVersion = '8.5.1'
mockitoVersion = '2.7.1'
}

View File

@@ -6,8 +6,9 @@ import com.task.data.remote.dto.NewsModel;
import javax.inject.Inject;
import rx.Observable;
import rx.schedulers.Schedulers;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
/**
* Created by AhmedEltaher on 5/12/2016

View File

@@ -10,13 +10,18 @@ import com.task.data.remote.service.NewsService;
import com.task.utils.Constants;
import com.task.utils.L;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import java.io.IOException;
import javax.inject.Inject;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import retrofit2.Call;
import rx.Observable;
import rx.Subscriber;
import static com.task.data.remote.ServiceError.NETWORK_ERROR;
import static com.task.utils.Constants.ERROR_UNDEFINED;
@@ -37,19 +42,31 @@ public class ApiRepository {
public Observable getNews() {
Observable<NewsModel> newsObservable = Observable.create(new Observable.OnSubscribe<NewsModel>() {
@Override
public void call(Subscriber<? super NewsModel> subscriber) {
if (!isConnected(App.getContext())) {
Exception e = new NetworkErrorException();
subscriber.onError(e);
} else {
NewsService newsService = serviceGenerator.createService(NewsService.class, Constants.BASE_URL);
ServiceResponse serviceResponse = processCall(newsService.fetchNews(), false);
NewsModel newsModel = (NewsModel) serviceResponse.getData();
subscriber.onNext(newsModel);
subscriber.onCompleted();
}
// Observable<NewsModel> newsObservable = Observable.create(new Observable.OnSubscribe<NewsModel>() {
// @Override
// public void call(Subscriber<? super NewsModel> subscriber) {
// if (!isConnected(App.getContext())) {
// Exception e = new NetworkErrorException();
// subscriber.onError(e);
// } else {
// NewsService newsService = serviceGenerator.createService(NewsService.class, Constants.BASE_URL);
// ServiceResponse serviceResponse = processCall(newsService.fetchNews(), false);
// NewsModel newsModel = (NewsModel) serviceResponse.getData();
// subscriber.onNext(newsModel);
// subscriber.onCompleted();
// }
// }
// });
Observable<NewsModel> newsObservable = Observable.create(newsModelObservableEmitter -> {
if (!isConnected(App.getContext())) {
Exception e = new NetworkErrorException();
newsModelObservableEmitter.onError(e);
} else {
NewsService newsService = serviceGenerator.createService(NewsService.class, Constants.BASE_URL);
ServiceResponse serviceResponse = processCall(newsService.fetchNews(), false);
NewsModel newsModel = (NewsModel) serviceResponse.getData();
newsModelObservableEmitter.onNext(newsModel);
newsModelObservableEmitter.onComplete();
}
});
return newsObservable;

View File

@@ -10,6 +10,8 @@ public class ServiceError {
private static final int GROUP_400 = 4;
private static final int GROUP_500 = 5;
private static final int VALUE_100 = 100;
public static final int SUCCESS_CODE = 200;
public static final int ERROR_CODE = 400;
private String description;
private int code;

View File

@@ -13,7 +13,7 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
@@ -49,7 +49,7 @@ public class ServiceGenerator {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl).client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build();
return retrofit.create(serviceClass);
}

View File

@@ -1,14 +1,15 @@
package com.task.di;
import com.task.data.local.LocalRepository;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.task.data.local.LocalRepository;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import io.reactivex.disposables.CompositeDisposable;
/**
* Created by AhmedEltaher on 5/12/2016
@@ -28,4 +29,11 @@ public class MainModule {
Gson gson = new GsonBuilder().create();
return gson;
}
@Provides
@Singleton
public CompositeDisposable provideCompositeSubscription() {
CompositeDisposable compositeDisposable = new CompositeDisposable();
return compositeDisposable;
}
}

View File

@@ -1,6 +1,7 @@
package com.task.ui.component.news;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import com.task.data.remote.dto.NewsItem;
import com.task.data.remote.dto.NewsModel;
@@ -13,7 +14,7 @@ import java.util.List;
import javax.inject.Inject;
import static android.text.TextUtils.isEmpty;
import static com.task.utils.ObjectUtil.isEmpty;
import static com.task.utils.ObjectUtil.isNull;
/**
@@ -75,6 +76,11 @@ public class HomePresenter extends Presenter<HomeView> {
getView().navigateToDetailsScreen(newsModel.getNewsItems().get(position));
};
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public NewsModel getNewsModel() {
return newsModel;
}
private final Callback callback = new Callback() {
@Override
public void onSuccess(NewsModel newsModel) {

View File

@@ -1,7 +1,5 @@
package com.task.usecase;
import android.support.annotation.NonNull;
import com.task.data.DataRepository;
import com.task.data.remote.dto.NewsItem;
import com.task.data.remote.dto.NewsModel;
@@ -10,32 +8,28 @@ import java.util.List;
import javax.inject.Inject;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
/**
* Created by AhmedEltaher on 5/12/2016
*/
public class NewsUseCase {
DataRepository dataRepository;
@NonNull
private CompositeSubscription mSubscriptions;
private DataRepository dataRepository;
private CompositeDisposable mSubscriptions;
@Inject
public NewsUseCase(DataRepository dataRepository) {
public NewsUseCase(DataRepository dataRepository,CompositeDisposable mSubscriptions) {
this.dataRepository = dataRepository;
this.mSubscriptions = new CompositeSubscription();
this.mSubscriptions = mSubscriptions;
}
public void getNews(final Callback callback) {
mSubscriptions.add(dataRepository.requestNews().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
mSubscriptions.add(dataRepository.requestNews().observeOn(AndroidSchedulers.mainThread())
.subscribe(newsModel -> callback.onSuccess(newsModel),
exception -> {
callback.onFail();
}));
exception -> callback.onFail()));
}
public NewsItem searchByTitle(List<NewsItem> news, String keyWord) {
@@ -48,7 +42,7 @@ public class NewsUseCase {
}
public void unSubscribe() {
mSubscriptions.unsubscribe();
mSubscriptions.clear();
}
public interface Callback {

View File

@@ -0,0 +1,106 @@
package com.task.ui.component.news;
import com.task.data.remote.dto.NewsItem;
import com.task.data.remote.dto.NewsModel;
import com.task.usecase.NewsUseCase;
import com.task.usecase.NewsUseCase.Callback;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class HomePresenterTest {
@Mock
private NewsUseCase newsUseCase;
@Mock
private NewsModel newsModelMock;
@Mock
private HomeView homeView;
@Mock
private Callback callback;
@Mock
private List<NewsItem> newsItems;
@Mock
private NewsItem newsItem;
private HomePresenter homePresenter;
private String newsTitle = "this is test";
private NewsModel newsModel;
private TestModelsGenerator testModelsGenerator;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
testModelsGenerator = new TestModelsGenerator();
newsModel = testModelsGenerator.generateNewsModel(newsTitle);
doAnswer(invocation -> {
((Callback) invocation.getArguments()[0]).onSuccess(newsModel);
return null;
}).when(newsUseCase).getNews(any(Callback.class));
homePresenter = new HomePresenter(newsUseCase);
homePresenter.setView(homeView);
}
@Test
public void getNewsList() {
// Let's do a synchronous answer for the callback
doAnswer(invocation -> {
((Callback) invocation.getArguments()[0]).onSuccess(newsModel);
return null;
}).when(newsUseCase).getNews(any(Callback.class));
homePresenter.getNews();
verify(homeView, times(1)).setLoaderVisibility(true);
verify(homeView, times(2)).setNoDataVisibility(false);
verify(homeView, times(1)).setListVisibility(false);
verify(newsUseCase, times(1)).getNews(any(Callback.class));
assertThat(homePresenter.getNewsModel(), is(equalTo(newsModel)));
}
@Test
public void testSearchSuccess() {
when(newsUseCase.searchByTitle(newsModel.getNewsItems(), newsTitle)).thenReturn(newsItem);
homePresenter.getNews();
homePresenter.onSearchClick(newsTitle);
verify(homeView, times(1)).navigateToDetailsScreen(any(NewsItem.class));
}
@Test
public void testSearchFailedWhileEmptyList() {
homePresenter.getNews();
homePresenter.onSearchClick(newsTitle);
assertThat(newsModelMock.getNewsItems().size(), equalTo(0));
verify(homeView, times(1)).showSearchError();
}
@Test
public void testSearchFailedWhenNothingMatches() {
when(newsUseCase.searchByTitle(any(), any())).thenReturn(null);
homePresenter.getNews();
homePresenter.onSearchClick(newsTitle);
verify(homeView, times(1)).showSearchError();
}
@After
public void tearDown() throws Exception {
}
}

View File

@@ -0,0 +1,51 @@
package com.task.ui.component.news;
import com.task.data.remote.ServiceResponse;
import com.task.data.remote.dto.NewsItem;
import com.task.data.remote.dto.NewsModel;
import java.util.ArrayList;
import java.util.List;
import static com.task.data.remote.ServiceError.ERROR_CODE;
import static com.task.data.remote.ServiceError.SUCCESS_CODE;
/**
* Created by ahmedeltaher on 3/8/17.
*/
public class TestModelsGenerator {
public NewsModel generateNewsModel(String stup) {
NewsModel newsModel = new NewsModel();
newsModel.setCopyright(stup);
newsModel.setLastUpdated(stup);
newsModel.setSection(stup);
newsModel.setStatus(stup);
newsModel.setNumResults(25L);
List<NewsItem> newsItems = new ArrayList<>();
for (int i = 0; i < 25; i++) {
newsItems.add(generateNewsItemModel(stup));
}
newsModel.setNewsItems(newsItems);
return newsModel;
}
public NewsItem generateNewsItemModel(String stup) {
NewsItem newsItem = new NewsItem();
newsItem.setTitle(stup);
newsItem.setAbstract(stup);
newsItem.setUrl(stup);
return newsItem;
}
public ServiceResponse getNewsSuccessfulModel() {
String stupString = "this is temp string";
NewsModel newsModel = generateNewsModel(stupString);
return new ServiceResponse(SUCCESS_CODE, newsModel);
}
public ServiceResponse getNewsErrorModel() {
return new ServiceResponse(ERROR_CODE, null);
}
}

View File

@@ -6,9 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
//DI
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
classpath 'com.android.tools.build:gradle:2.3.0'
//lamda
classpath 'me.tatarka:gradle-retrolambda:3.2.5'
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'

View File

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Mon Dec 28 10:00:20 PST 2015
#Fri Mar 10 17:20:13 CET 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip