From ee817d15065bc07b68cb9920df46cc80cd510660 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Sun, 31 Aug 2025 11:51:08 +0300 Subject: [PATCH 1/4] Add unit tests for repositories and home view model --- app/build.gradle | 6 ++ .../repository/DefaultHomeRepositoryTest.java | 64 ++++++++++++++++ .../repository/DefaultQuizRepositoryTest.java | 36 +++++++++ .../ui/screens/home/HomeViewModelTest.java | 74 +++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultHomeRepositoryTest.java create mode 100644 app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java create mode 100644 app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java 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()); + } +} From 83239217f6ce06d672d0199a5aaa63ce729144d4 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Sun, 31 Aug 2025 12:10:27 +0300 Subject: [PATCH 2/4] Use Java 17 for compilation --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 57a4ea7c..33b0b6ef 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,8 +36,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_21 - targetCompatibility JavaVersion.VERSION_21 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } buildFeatures { From 107aa2daab2fe3c62c836cbdab3d041c74544467 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Sun, 31 Aug 2025 13:23:17 +0300 Subject: [PATCH 3/4] Compile with Java 21 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 33b0b6ef..57a4ea7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,8 +36,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } buildFeatures { From 5d3662b568cb7bfa00e2e24f349ac21b91c13e4f Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Sun, 31 Aug 2025 13:28:35 +0300 Subject: [PATCH 4/4] chore(ci): build with JDK 21 --- .github/workflows/android.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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