diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index d89bb99d65148fc964687815b11160c69ede5121..376628a3f2b171a13356e0cf701344c994d1dd78 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/.travis.yml b/.travis.yml index 705321668533cf82bb1b7abe9c9c71782112b64e..7f4a898138b8015f0cbcbe071cf38b721f9d1a20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ android: - build-tools-26.0.2 - build-tools-27.0.3 - android-22 - - android-26 + - android-27 - add-on - extra - platform-tools @@ -12,7 +12,7 @@ android: - extra-google-googleplayservices - extra-google-m2repository - extra-android-m2repository - - addon-google_apis-google-26 + - addon-google_apis-google-27 - sys-img-armeabi-v7a-android-22 env: global: @@ -21,7 +21,7 @@ env: sudo: required before_install: - - yes | sdkmanager "platforms;android-26" + - yes | sdkmanager "platforms;android-27" before_script: - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a diff --git a/app/build.gradle b/app/build.gradle index 87c3accf67628aad5f9194b351f1126065191cc5..4a0020d2feeed75159f263c74a8c6ca33c7d6ba6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 26 + compileSdkVersion 27 defaultConfig { applicationId "me.szaki.xkcd.xkcdbrowser" minSdkVersion 22 - targetSdkVersion 26 + targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -55,13 +55,13 @@ ext { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:26.1.0' - implementation 'com.android.support:support-v4:26.1.0' - implementation 'com.android.support:design:26.1.0' + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:support-v4:27.1.1' + implementation 'com.android.support:design:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.0' testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' // Dagger implementation 'com.google.dagger:dagger:2.11' @@ -86,4 +86,13 @@ dependencies { // AndroidSwipeLayout implementation 'com.daimajia.swipelayout:library:1.2.0@aar' + + //Unit Test - Robolectric + //JUnit - JUnit 5 is not supported yet + testImplementation 'junit:junit:4.12' + testImplementation 'org.robolectric:robolectric:3.7.1' + testImplementation 'org.mockito:mockito-core:2.15.0' + testImplementation 'com.google.dagger:dagger:2.11' + testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.11' + testCompileOnly 'javax.annotation:jsr250-api:1.0' } diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/ExampleUnitTest.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/ExampleUnitTest.java deleted file mode 100644 index eeb658e7e6e8b32afcfae5727aa5660802bbc33f..0000000000000000000000000000000000000000 --- a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.szaki.xkcd.xkcdbrowser; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/TestHelper.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/TestHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..a705e61b7ef52566112d22595e3d237500a9cf46 --- /dev/null +++ b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/TestHelper.java @@ -0,0 +1,20 @@ +package me.szaki.xkcd.xkcdbrowser; + +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowLog; + +import java.util.List; + +import me.szaki.xkcd.xkcdbrowser.mock.dagger.DaggerMockComponent; +import me.szaki.xkcd.xkcdbrowser.ui.UIModule; + +public class TestHelper { + public static void setTestInjector() { + ShadowLog.stream = System.out; + XKCDBrowserApplication application = (XKCDBrowserApplication) RuntimeEnvironment.application; + XKCDBrowserApplicationComponent injector = DaggerMockComponent.builder() + .uIModule(new UIModule(application.getApplicationContext())) + .build(); + application.injector = injector; + } +} diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/api/MockAPI.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/api/MockAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..fdae20c7426760ff9b9e575c0260cd41ad1feba2 --- /dev/null +++ b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/api/MockAPI.java @@ -0,0 +1,119 @@ +package me.szaki.xkcd.xkcdbrowser.mock.api; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import me.szaki.xkcd.xkcdbrowser.network.api.ComicApi; +import me.szaki.xkcd.xkcdbrowser.network.model.Comic; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class MockAPI implements ComicApi { + @Override + public Call<Comic> getCurrentComic() { + final Comic currentComic = new Comic(){{ + setImg("https://cataas.com/c"); + setDay("4"); + setMonth("4"); + setNum(10); + setTitle("Title"); + setYear("2000"); + }}; + + Call<Comic> call = new Call<Comic>() { + @Override + public Response<Comic> execute() throws IOException { + return Response.success(currentComic); + } + + @Override + public void enqueue(Callback<Comic> callback) { + + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public void cancel() { + + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public Call<Comic> clone() { + return null; + } + }; + + return call; + } + + @Override + public Call<Comic> getComic(Long id) { + Comic comic = null; + if (10L == id) { + comic = new Comic() {{ + setImg("https://cataas.com/c"); + setDay("4"); + setMonth("4"); + setNum(10); + setTitle("Title"); + setYear("2000"); + }}; + + } else if (9L == id) { + comic = new Comic() {{ + setImg("https://cataas.com/cat"); + setDay("1"); + setMonth("1"); + setNum(9); + setTitle("Tittle"); + setYear("1999"); + }}; + } + + final Comic finalComic = comic; + Call<Comic> call = new Call<Comic>() { + @Override + public Response<Comic> execute() throws IOException { + return Response.success(finalComic); + } + + @Override + public void enqueue(Callback<Comic> callback) { + + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public void cancel() { + + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public Call<Comic> clone() { + return null; + } + }; + + return call; + } +} diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockComponent.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..57dd10f4415b087b673ab7f009f522667473abb4 --- /dev/null +++ b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockComponent.java @@ -0,0 +1,13 @@ +package me.szaki.xkcd.xkcdbrowser.mock.dagger; + +import javax.inject.Singleton; + +import dagger.Component; +import me.szaki.xkcd.xkcdbrowser.XKCDBrowserApplicationComponent; +import me.szaki.xkcd.xkcdbrowser.interactor.InteractorModule; +import me.szaki.xkcd.xkcdbrowser.ui.UIModule; + +@Singleton +@Component(modules = {UIModule.class, InteractorModule.class, MockNetworkModule.class, MockDBModule.class}) +public interface MockComponent extends XKCDBrowserApplicationComponent { +} diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockDBModule.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockDBModule.java new file mode 100644 index 0000000000000000000000000000000000000000..ac4dafc83f3ebb0b225427ee40e05846e5785896 --- /dev/null +++ b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockDBModule.java @@ -0,0 +1,17 @@ +package me.szaki.xkcd.xkcdbrowser.mock.dagger; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import me.szaki.xkcd.xkcdbrowser.database.ComicsDatabase; +import me.szaki.xkcd.xkcdbrowser.mock.db.MockComicDB; + +@Module +public class MockDBModule { + @Provides + @Singleton + public ComicsDatabase provideComicsDatabase() { + return new MockComicDB(); + } +} diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockNetworkModule.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockNetworkModule.java new file mode 100644 index 0000000000000000000000000000000000000000..d7eccc87ed1e211a443ecf8c518905fd9a142c85 --- /dev/null +++ b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/dagger/MockNetworkModule.java @@ -0,0 +1,17 @@ +package me.szaki.xkcd.xkcdbrowser.mock.dagger; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import me.szaki.xkcd.xkcdbrowser.mock.api.MockAPI; +import me.szaki.xkcd.xkcdbrowser.network.api.ComicApi; + +@Module +public class MockNetworkModule { + @Singleton + @Provides + public ComicApi provideComicApi() { + return new MockAPI(); + } +} diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/db/MockComicDB.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/db/MockComicDB.java new file mode 100644 index 0000000000000000000000000000000000000000..1b2461d1dd569afdf5f3c2cdd879a36e2091ddcb --- /dev/null +++ b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/db/MockComicDB.java @@ -0,0 +1,28 @@ +package me.szaki.xkcd.xkcdbrowser.mock.db; + +import android.arch.persistence.db.SupportSQLiteOpenHelper; +import android.arch.persistence.room.DatabaseConfiguration; +import android.arch.persistence.room.InvalidationTracker; + +import me.szaki.xkcd.xkcdbrowser.database.ComicsDAO; +import me.szaki.xkcd.xkcdbrowser.database.ComicsDatabase; + + +public class MockComicDB extends ComicsDatabase { + private MockDAO mockDAO = new MockDAO(); + + @Override + public ComicsDAO getDAO() { + return mockDAO; + } + + @Override + protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) { + return null; + } + + @Override + protected InvalidationTracker createInvalidationTracker() { + return null; + } +} diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/db/MockDAO.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/db/MockDAO.java new file mode 100644 index 0000000000000000000000000000000000000000..656f20f1ada99fe20c3069e82ae2d9f6693a6536 --- /dev/null +++ b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/mock/db/MockDAO.java @@ -0,0 +1,65 @@ +package me.szaki.xkcd.xkcdbrowser.mock.db; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import me.szaki.xkcd.xkcdbrowser.database.ComicsDAO; +import me.szaki.xkcd.xkcdbrowser.model.ComicStrip; + + +public class MockDAO implements ComicsDAO { + List<ComicStrip> comicStrip = new ArrayList<>(); + + public MockDAO() { + comicStrip.add(new ComicStrip(){{ + setImg("https://cataas.com/c"); + setDay("4"); + setMonth("4"); + setNum(10L); + setTitle("Title"); + setYear("2000"); + }}); + } + + @Override + public List<ComicStrip> getAllComics() { + return comicStrip; + } + + @Override + public ComicStrip getComic(Long id) { + return comicStrip.get(Math.toIntExact(id)); + } + + @Override + public ComicStrip getComicByNum(Long num) { + for (ComicStrip c: comicStrip) { + if (c.getNum().equals(num)){ + return c; + } + } + return null; + } + + @Override + public void insertAll(ComicStrip... comic) { + comicStrip.addAll(Arrays.asList(comic)); + } + + @Override + public void update(ComicStrip comic) { + delete(comic); + comicStrip.add(comic); + } + + @Override + public void delete(ComicStrip comic) { + for (ComicStrip c: comicStrip) { + if (c.equals(comic)){ + comicStrip.remove(c); + return; + } + } + } +} diff --git a/app/src/test/java/me/szaki/xkcd/xkcdbrowser/test/MainTest.java b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/test/MainTest.java new file mode 100644 index 0000000000000000000000000000000000000000..759597ea923381ffa95bdf507ede229f9ed470c7 --- /dev/null +++ b/app/src/test/java/me/szaki/xkcd/xkcdbrowser/test/MainTest.java @@ -0,0 +1,144 @@ +package me.szaki.xkcd.xkcdbrowser.test; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import me.szaki.xkcd.xkcdbrowser.BuildConfig; +import me.szaki.xkcd.xkcdbrowser.TestHelper; +import me.szaki.xkcd.xkcdbrowser.network.model.Comic; +import me.szaki.xkcd.xkcdbrowser.ui.main.MainPresenter; +import me.szaki.xkcd.xkcdbrowser.ui.main.MainScreen; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static junit.framework.Assert.assertEquals; + +@RunWith(RobolectricTestRunner.class) +@Config(constants = BuildConfig.class) +public class MainTest { + private MainPresenter mainPresenter; + private MainScreen mainScreen; + private CountDownLatch latch; + + @Before + public void Setup() { + TestHelper.setTestInjector(); + mainScreen = mock(MainScreen.class); + mainPresenter = new MainPresenter(); + mainPresenter.attachScreen(mainScreen); + latch = new CountDownLatch(1); + } + + @Test + public void getCurrentComic() throws InterruptedException { + mainPresenter.getCurrent(); + + latch.await(200, TimeUnit.MILLISECONDS); + + ArgumentCaptor<Comic> comiCaptor = ArgumentCaptor.forClass(Comic.class); + verify(mainScreen).getImage(comiCaptor.capture()); + + assertEquals(10, (int) comiCaptor.getValue().getNum()); + assertEquals("4", comiCaptor.getValue().getDay()); + assertEquals("4", comiCaptor.getValue().getMonth()); + assertEquals("2000", comiCaptor.getValue().getYear()); + assertEquals("Title", comiCaptor.getValue().getTitle()); + assertEquals("https://cataas.com/c", comiCaptor.getValue().getImg()); + } + + @Test + public void getPrevious() throws InterruptedException { + mainPresenter.getCurrent(); + latch.await(200, TimeUnit.MILLISECONDS); + reset(mainScreen); + + mainPresenter.getPrevious(); + + latch.await(200, TimeUnit.MILLISECONDS); + + ArgumentCaptor<Comic> comiCaptor = ArgumentCaptor.forClass(Comic.class); + verify(mainScreen).getImage(comiCaptor.capture()); + + assertEquals(9, (int) comiCaptor.getValue().getNum()); + assertEquals("1", comiCaptor.getValue().getDay()); + assertEquals("1", comiCaptor.getValue().getMonth()); + assertEquals("1999", comiCaptor.getValue().getYear()); + assertEquals("Tittle", comiCaptor.getValue().getTitle()); + assertEquals("https://cataas.com/cat", comiCaptor.getValue().getImg()); + } + + @Test + public void getRandom() { + + } + + @Test + public void getNext() throws InterruptedException { + mainPresenter.getCurrent(); + latch.await(200, TimeUnit.MILLISECONDS); + + mainPresenter.getPrevious(); + latch.await(200, TimeUnit.MILLISECONDS); + reset(mainScreen); + + mainPresenter.getNext(); + + latch.await(200, TimeUnit.MILLISECONDS); + + ArgumentCaptor<Comic> comiCaptor = ArgumentCaptor.forClass(Comic.class); + verify(mainScreen).getImage(comiCaptor.capture()); + + assertEquals(10, (int) comiCaptor.getValue().getNum()); + assertEquals("4", comiCaptor.getValue().getDay()); + assertEquals("4", comiCaptor.getValue().getMonth()); + assertEquals("2000", comiCaptor.getValue().getYear()); + assertEquals("Title", comiCaptor.getValue().getTitle()); + assertEquals("https://cataas.com/c", comiCaptor.getValue().getImg()); + } + + @Test + public void getNextIfLast() throws InterruptedException { + mainPresenter.getCurrent(); + latch.await(200, TimeUnit.MILLISECONDS); + reset(mainScreen); + + mainPresenter.getNext(); + + latch.await(200, TimeUnit.MILLISECONDS); + + verify(mainScreen).getImage(null); + } + + + @Test + public void saveComic() throws InterruptedException { + mainPresenter.getCurrent(); + latch.await(200, TimeUnit.MILLISECONDS); + ArgumentCaptor<Comic> comiCaptor = ArgumentCaptor.forClass(Comic.class); + verify(mainScreen).getImage(comiCaptor.capture()); + + mainPresenter.saveComic(comiCaptor.getValue()); + latch.await(200, TimeUnit.MILLISECONDS); + verify(mainScreen).saveComic(); + + reset(mainScreen); + mainPresenter.getCurrent(); + latch.await(200, TimeUnit.MILLISECONDS); + verify(mainScreen).setFavorite(true); + } + + @After + public void Teardown() { + mainPresenter.detachScreen(); + } +}