Skip to content

Commit e373e80

Browse files
Add comprehensive ViewModel unit tests
1 parent 3fe6eaa commit e373e80

File tree

6 files changed

+502
-21
lines changed

6 files changed

+502
-21
lines changed

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/CodeViewModel.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,22 @@
88
import com.d4rk.androidtutorials.java.ui.screens.android.repository.LessonRepository;
99

1010
public class CodeViewModel extends ViewModel {
11-
private final MutableLiveData<LessonRepository.Lesson> lesson = new MutableLiveData<>();
12-
private final LessonRepository repository = new LessonRepository();
13-
private final GetLessonUseCase getLessonUseCase = new GetLessonUseCase(repository);
11+
private final MutableLiveData<com.d4rk.androidtutorials.java.data.repository.LessonRepository.Lesson> lesson = new MutableLiveData<>();
12+
private final GetLessonUseCase getLessonUseCase;
13+
14+
public CodeViewModel() {
15+
this(new GetLessonUseCase(new LessonRepository()));
16+
}
17+
18+
CodeViewModel(GetLessonUseCase getLessonUseCase) {
19+
this.getLessonUseCase = getLessonUseCase;
20+
}
1421

1522
public void setLessonName(String lessonName) {
1623
lesson.setValue(getLessonUseCase.invoke(lessonName));
1724
}
1825

19-
public LiveData<LessonRepository.Lesson> getLesson() {
26+
public LiveData<com.d4rk.androidtutorials.java.data.repository.LessonRepository.Lesson> getLesson() {
2027
return lesson;
2128
}
2229
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.d4rk.androidtutorials.java.ui.screens.android;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
6+
7+
import com.d4rk.androidtutorials.java.data.repository.LessonRepository;
8+
import com.d4rk.androidtutorials.java.domain.android.GetLessonUseCase;
9+
10+
import org.junit.Rule;
11+
import org.junit.Test;
12+
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
16+
public class CodeViewModelTest {
17+
18+
@Rule
19+
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
20+
21+
@Test
22+
public void setLessonName_updatesLiveData() {
23+
FakeLessonRepository repository = new FakeLessonRepository();
24+
LessonRepository.Lesson expected = new LessonRepository.Lesson(1, 2, 3);
25+
repository.addLesson("lesson", expected);
26+
CodeViewModel viewModel = new CodeViewModel(new GetLessonUseCase(repository));
27+
28+
viewModel.setLessonName("lesson");
29+
30+
assertEquals(expected, viewModel.getLesson().getValue());
31+
}
32+
33+
@Test(expected = IllegalArgumentException.class)
34+
public void setLessonName_unknownLessonThrows() {
35+
FakeLessonRepository repository = new FakeLessonRepository();
36+
CodeViewModel viewModel = new CodeViewModel(new GetLessonUseCase(repository));
37+
38+
viewModel.setLessonName("unknown");
39+
}
40+
41+
private static final class FakeLessonRepository implements LessonRepository {
42+
private final Map<String, Lesson> lessons = new HashMap<>();
43+
44+
void addLesson(String name, Lesson lesson) {
45+
lessons.put(name, lesson);
46+
}
47+
48+
@Override
49+
public Lesson getLesson(String lessonName) {
50+
Lesson lesson = lessons.get(lessonName);
51+
if (lesson == null) {
52+
throw new IllegalArgumentException("Unknown lesson: " + lessonName);
53+
}
54+
return lesson;
55+
}
56+
}
57+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.d4rk.androidtutorials.java.ui.screens.help;
2+
3+
import static org.junit.Assert.assertSame;
4+
5+
import android.app.Activity;
6+
7+
import com.d4rk.androidtutorials.java.data.repository.HelpRepository;
8+
import com.d4rk.androidtutorials.java.domain.help.LaunchReviewFlowUseCase;
9+
import com.d4rk.androidtutorials.java.domain.help.RequestReviewFlowUseCase;
10+
import com.google.android.play.core.review.ReviewInfo;
11+
12+
import org.junit.Test;
13+
import org.mockito.Mockito;
14+
15+
public class HelpViewModelTest {
16+
17+
@Test
18+
public void requestReviewFlowDelegatesToRepository() {
19+
FakeHelpRepository repository = new FakeHelpRepository();
20+
HelpViewModel viewModel = new HelpViewModel(
21+
new RequestReviewFlowUseCase(repository),
22+
new LaunchReviewFlowUseCase(repository)
23+
);
24+
HelpRepository.OnReviewInfoListener listener = Mockito.mock(HelpRepository.OnReviewInfoListener.class);
25+
26+
viewModel.requestReviewFlow(listener);
27+
28+
assertSame(listener, repository.lastListener);
29+
}
30+
31+
@Test
32+
public void launchReviewFlowDelegatesToRepository() {
33+
FakeHelpRepository repository = new FakeHelpRepository();
34+
HelpViewModel viewModel = new HelpViewModel(
35+
new RequestReviewFlowUseCase(repository),
36+
new LaunchReviewFlowUseCase(repository)
37+
);
38+
Activity activity = Mockito.mock(Activity.class);
39+
ReviewInfo info = Mockito.mock(ReviewInfo.class);
40+
41+
viewModel.launchReviewFlow(activity, info);
42+
43+
assertSame(activity, repository.lastActivity);
44+
assertSame(info, repository.lastReviewInfo);
45+
}
46+
47+
private static final class FakeHelpRepository implements HelpRepository {
48+
private HelpRepository.OnReviewInfoListener lastListener;
49+
private Activity lastActivity;
50+
private ReviewInfo lastReviewInfo;
51+
52+
@Override
53+
public void requestReviewFlow(OnReviewInfoListener listener) {
54+
lastListener = listener;
55+
}
56+
57+
@Override
58+
public void launchReviewFlow(Activity activity, ReviewInfo reviewInfo) {
59+
lastActivity = activity;
60+
lastReviewInfo = reviewInfo;
61+
}
62+
}
63+
}

app/src/test/java/com/d4rk/androidtutorials/java/ui/screens/home/HomeViewModelTest.java

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import static org.junit.Assert.assertNotNull;
55
import static org.junit.Assert.assertTrue;
66

7+
import android.content.Intent;
8+
import android.net.Uri;
9+
710
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
811

912
import com.d4rk.androidtutorials.java.data.model.PromotedApp;
@@ -16,7 +19,10 @@
1619
import org.junit.Rule;
1720
import org.junit.Test;
1821

22+
import java.util.ArrayList;
23+
import java.util.HashMap;
1924
import java.util.List;
25+
import java.util.Map;
2026

2127

2228
public class HomeViewModelTest {
@@ -28,12 +34,7 @@ public class HomeViewModelTest {
2834
public void uiStateUpdatesWithData() {
2935
List<PromotedApp> promoted = List.of(new PromotedApp("App", "pkg", "icon"));
3036
FakeHomeRepository repo = new FakeHomeRepository(promoted);
31-
HomeViewModel viewModel = new HomeViewModel(
32-
new GetDailyTipUseCase(repo),
33-
new GetPromotedAppsUseCase(repo),
34-
new GetPlayStoreUrlUseCase(repo),
35-
new GetAppPlayStoreUrlUseCase(repo)
36-
);
37+
HomeViewModel viewModel = createViewModel(repo);
3738
viewModel.setAnnouncements("Title", "Subtitle");
3839

3940
HomeUiState state = viewModel.getUiState().getValue();
@@ -47,39 +48,118 @@ public void uiStateUpdatesWithData() {
4748
@Test
4849
public void uiStateHandlesEmptyPromotedApps() {
4950
FakeHomeRepository repo = new FakeHomeRepository(List.of());
50-
HomeViewModel viewModel = new HomeViewModel(
51-
new GetDailyTipUseCase(repo),
52-
new GetPromotedAppsUseCase(repo),
53-
new GetPlayStoreUrlUseCase(repo),
54-
new GetAppPlayStoreUrlUseCase(repo)
55-
);
51+
HomeViewModel viewModel = createViewModel(repo);
5652
viewModel.setAnnouncements("Title", "Subtitle");
5753

5854
HomeUiState state = viewModel.getUiState().getValue();
5955
assertNotNull(state);
6056
assertTrue(state.promotedApps().isEmpty());
6157
}
6258

63-
record FakeHomeRepository(List<PromotedApp> apps) implements HomeRepository {
59+
@Test
60+
public void promotedAppsLimitedToAtMostFour() {
61+
List<PromotedApp> promoted = List.of(
62+
new PromotedApp("App1", "pkg1", "icon1"),
63+
new PromotedApp("App2", "pkg2", "icon2"),
64+
new PromotedApp("App3", "pkg3", "icon3"),
65+
new PromotedApp("App4", "pkg4", "icon4"),
66+
new PromotedApp("App5", "pkg5", "icon5")
67+
);
68+
FakeHomeRepository repo = new FakeHomeRepository(promoted);
69+
HomeViewModel viewModel = createViewModel(repo);
70+
71+
HomeUiState state = viewModel.getUiState().getValue();
72+
assertNotNull(state);
73+
assertTrue(state.promotedApps().size() <= 4);
74+
}
75+
76+
@Test
77+
public void getOpenPlayStoreIntent_buildsViewIntent() {
78+
FakeHomeRepository repo = new FakeHomeRepository(List.of());
79+
String expectedUrl = "https://play.google.com/store/apps/details?id=com.example";
80+
repo.setPlayStoreUrl(expectedUrl);
81+
HomeViewModel viewModel = createViewModel(repo);
82+
83+
Intent intent = viewModel.getOpenPlayStoreIntent();
84+
85+
assertEquals(Intent.ACTION_VIEW, intent.getAction());
86+
assertEquals(Uri.parse(expectedUrl), intent.getData());
87+
}
88+
89+
@Test
90+
public void getPromotedAppIntent_buildsViewIntent() {
91+
FakeHomeRepository repo = new FakeHomeRepository(List.of());
92+
String packageName = "pkg";
93+
String expectedUrl = "https://play.google.com/store/apps/details?id=" + packageName;
94+
repo.setAppUrl(packageName, expectedUrl);
95+
HomeViewModel viewModel = createViewModel(repo);
96+
97+
Intent intent = viewModel.getPromotedAppIntent(packageName);
98+
99+
assertEquals(Intent.ACTION_VIEW, intent.getAction());
100+
assertEquals(Uri.parse(expectedUrl), intent.getData());
101+
}
102+
103+
@Test
104+
public void getLearnMoreIntent_targetsAndroidDevelopers() {
105+
FakeHomeRepository repo = new FakeHomeRepository(List.of());
106+
HomeViewModel viewModel = createViewModel(repo);
107+
108+
Intent intent = viewModel.getLearnMoreIntent();
109+
110+
assertEquals(Intent.ACTION_VIEW, intent.getAction());
111+
assertEquals(Uri.parse("https://developer.android.com"), intent.getData());
112+
}
113+
114+
private HomeViewModel createViewModel(FakeHomeRepository repo) {
115+
return new HomeViewModel(
116+
new GetDailyTipUseCase(repo),
117+
new GetPromotedAppsUseCase(repo),
118+
new GetPlayStoreUrlUseCase(repo),
119+
new GetAppPlayStoreUrlUseCase(repo)
120+
);
121+
}
122+
123+
private static final class FakeHomeRepository implements HomeRepository {
124+
private final List<PromotedApp> promotedApps;
125+
private String dailyTip = "tip";
126+
private String playStoreUrl = "https://play.google.com/store/apps/details?id=default";
127+
private final Map<String, String> appUrls = new HashMap<>();
128+
129+
FakeHomeRepository(List<PromotedApp> promotedApps) {
130+
this.promotedApps = new ArrayList<>(promotedApps);
131+
}
132+
133+
void setDailyTip(String dailyTip) {
134+
this.dailyTip = dailyTip;
135+
}
136+
137+
void setPlayStoreUrl(String playStoreUrl) {
138+
this.playStoreUrl = playStoreUrl;
139+
}
140+
141+
void setAppUrl(String packageName, String url) {
142+
appUrls.put(packageName, url);
143+
}
64144

65145
@Override
66146
public String dailyTip() {
67-
return "tip";
147+
return dailyTip;
68148
}
69149

70150
@Override
71151
public String getPlayStoreUrl() {
72-
return "";
152+
return playStoreUrl;
73153
}
74154

75155
@Override
76156
public String getAppPlayStoreUrl(String packageName) {
77-
return "";
157+
return appUrls.getOrDefault(packageName, "");
78158
}
79159

80160
@Override
81161
public void fetchPromotedApps(PromotedAppsCallback callback) {
82-
callback.onResult(apps);
162+
callback.onResult(promotedApps);
83163
}
84164
}
85165
}

0 commit comments

Comments
 (0)