diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 195d3a9e..a1d64457 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -13,10 +13,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: set up JDK 17 + - name: set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: gradle diff --git a/app/build.gradle b/app/build.gradle index e23f14e2..57a4ea7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -96,4 +96,10 @@ dependencies { implementation libs.codeview implementation libs.hilt.android annotationProcessor libs.hilt.compiler + + // Testing + testImplementation 'junit:junit:4.13.2' + testImplementation 'androidx.arch.core:core-testing:2.2.0' + testImplementation 'org.mockito:mockito-core:5.12.0' + testImplementation 'org.mockito:mockito-inline:5.2.0' } \ No newline at end of file diff --git a/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepositoryTest.java b/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepositoryTest.java new file mode 100644 index 00000000..a7b66a57 --- /dev/null +++ b/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepositoryTest.java @@ -0,0 +1,64 @@ +package com.d4rk.androidtutorials.java.data.repository; + +import com.d4rk.androidtutorials.java.data.model.PromotedApp; +import com.d4rk.androidtutorials.java.data.source.HomeLocalDataSource; +import com.d4rk.androidtutorials.java.data.source.HomeRemoteDataSource; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +public class DefaultHomeRepositoryTest { + + private static class FakeHomeLocalDataSource implements HomeLocalDataSource { + @Override + public String getPlayStoreUrl() { + return "play"; + } + + @Override + public String getAppPlayStoreUrl(String packageName) { + return "play/" + packageName; + } + + @Override + public String getDailyTip() { + return "tip"; + } + } + + private static class FakeHomeRemoteDataSource implements HomeRemoteDataSource { + private final List apps; + boolean called = false; + + FakeHomeRemoteDataSource(List apps) { + this.apps = apps; + } + + @Override + public void fetchPromotedApps(PromotedAppsCallback callback) { + called = true; + callback.onResult(apps); + } + } + + @Test + public void repositoryDelegatesToDataSources() { + List promoted = List.of(new PromotedApp("Name", "pkg", "icon")); + FakeHomeRemoteDataSource remote = new FakeHomeRemoteDataSource(promoted); + FakeHomeLocalDataSource local = new FakeHomeLocalDataSource(); + + DefaultHomeRepository repository = new DefaultHomeRepository(remote, local); + + assertEquals("play", repository.getPlayStoreUrl()); + assertEquals("play/pkg", repository.getAppPlayStoreUrl("pkg")); + assertEquals("tip", repository.getDailyTip()); + + final List[] result = new List[1]; + repository.fetchPromotedApps(apps -> result[0] = apps); + assertTrue(remote.called); + assertEquals(promoted, result[0]); + } +} diff --git a/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java b/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java new file mode 100644 index 00000000..fa2809a5 --- /dev/null +++ b/app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java @@ -0,0 +1,36 @@ +package com.d4rk.androidtutorials.java.data.repository; + +import com.d4rk.androidtutorials.java.data.model.QuizQuestion; +import com.d4rk.androidtutorials.java.data.source.QuizLocalDataSource; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +public class DefaultQuizRepositoryTest { + + private static class FakeQuizLocalDataSource implements QuizLocalDataSource { + private final List questions; + + FakeQuizLocalDataSource(List questions) { + this.questions = questions; + } + + @Override + public List loadQuestions() { + return questions; + } + } + + @Test + public void loadQuestionsReturnsLocalData() { + List expected = List.of( + new QuizQuestion("Q", new String[]{"A", "B"}, 0) + ); + FakeQuizLocalDataSource local = new FakeQuizLocalDataSource(expected); + DefaultQuizRepository repository = new DefaultQuizRepository(local); + assertEquals(expected, repository.loadQuestions()); + } +} diff --git a/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java b/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java new file mode 100644 index 00000000..7be4482b --- /dev/null +++ b/app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java @@ -0,0 +1,74 @@ +package com.d4rk.androidtutorials.java.ui.screens.home; + +import android.app.Application; + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule; + +import com.d4rk.androidtutorials.java.R; +import com.d4rk.androidtutorials.java.data.model.PromotedApp; +import com.d4rk.androidtutorials.java.data.repository.HomeRepository; +import com.d4rk.androidtutorials.java.domain.home.GetDailyTipUseCase; +import com.d4rk.androidtutorials.java.domain.home.GetPromotedAppsUseCase; + +import org.junit.Rule; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +import org.mockito.Mockito; + +public class HomeViewModelTest { + + @Rule + public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); + + + static class FakeHomeRepository implements HomeRepository { + final String dailyTip; + final List apps; + + FakeHomeRepository(String dailyTip, List apps) { + this.dailyTip = dailyTip; + this.apps = apps; + } + + @Override public String getPlayStoreUrl() { return ""; } + @Override public String getAppPlayStoreUrl(String packageName) { return ""; } + @Override public String getDailyTip() { return dailyTip; } + @Override public void fetchPromotedApps(PromotedAppsCallback callback) { callback.onResult(apps); } + } + + @Test + public void uiStateUpdatesWithData() { + List promoted = List.of(new PromotedApp("App", "pkg", "icon")); + FakeHomeRepository repo = new FakeHomeRepository("tip", promoted); + Application app = Mockito.mock(Application.class); + Mockito.when(app.getString(R.string.announcement_title)).thenReturn("Title"); + Mockito.when(app.getString(R.string.announcement_subtitle)).thenReturn("Subtitle"); + HomeViewModel viewModel = new HomeViewModel(app, repo, + new GetDailyTipUseCase(repo), new GetPromotedAppsUseCase(repo)); + + HomeUiState state = viewModel.getUiState().getValue(); + assertNotNull(state); + assertEquals("Title", state.announcementTitle()); + assertEquals("Subtitle", state.announcementSubtitle()); + assertEquals("tip", state.dailyTip()); + assertEquals(promoted, state.promotedApps()); + } + + @Test + public void uiStateHandlesEmptyPromotedApps() { + FakeHomeRepository repo = new FakeHomeRepository("tip", List.of()); + Application app = Mockito.mock(Application.class); + Mockito.when(app.getString(R.string.announcement_title)).thenReturn("Title"); + Mockito.when(app.getString(R.string.announcement_subtitle)).thenReturn("Subtitle"); + HomeViewModel viewModel = new HomeViewModel(app, repo, + new GetDailyTipUseCase(repo), new GetPromotedAppsUseCase(repo)); + + HomeUiState state = viewModel.getUiState().getValue(); + assertNotNull(state); + assertTrue(state.promotedApps().isEmpty()); + } +}