Skip to content

Commit fe1bc75

Browse files
Add tests for DefaultSupportRepository
1 parent a82cdd8 commit fe1bc75

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package com.d4rk.androidtutorials.java.data.repository;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertNull;
6+
import static org.junit.Assert.assertSame;
7+
8+
import android.app.Activity;
9+
import android.content.Context;
10+
11+
import com.android.billingclient.api.BillingClient;
12+
import com.android.billingclient.api.BillingFlowParams;
13+
import com.android.billingclient.api.BillingResult;
14+
import com.android.billingclient.api.ProductDetails;
15+
import com.d4rk.androidtutorials.java.ads.AdUtils;
16+
import com.d4rk.androidtutorials.java.data.repository.SupportRepository.BillingFlowLauncher;
17+
import com.google.android.gms.ads.AdRequest;
18+
19+
import org.junit.Before;
20+
import org.junit.Test;
21+
import org.mockito.ArgumentCaptor;
22+
import org.mockito.MockedStatic;
23+
import org.mockito.Mockito;
24+
25+
import java.lang.reflect.Field;
26+
import java.lang.reflect.Method;
27+
import java.util.List;
28+
import java.util.Map;
29+
import java.util.concurrent.atomic.AtomicReference;
30+
31+
public class DefaultSupportRepositoryTest {
32+
33+
private Context context;
34+
private BillingClient billingClient;
35+
private DefaultSupportRepository repository;
36+
37+
@Before
38+
public void setUp() throws Exception {
39+
context = Mockito.mock(Context.class);
40+
Mockito.when(context.getApplicationContext()).thenReturn(context);
41+
42+
repository = new DefaultSupportRepository(context);
43+
44+
billingClient = Mockito.mock(BillingClient.class);
45+
setBillingClient(repository, billingClient);
46+
}
47+
48+
@Test
49+
public void queryProductDetailsPopulatesCacheAndNotifiesListener() throws Exception {
50+
Mockito.when(billingClient.isReady()).thenReturn(true);
51+
52+
ProductDetails productDetails = Mockito.mock(ProductDetails.class);
53+
Mockito.when(productDetails.getProductId()).thenReturn("donation");
54+
List<ProductDetails> detailsList = List.of(productDetails);
55+
56+
Mockito.doAnswer(invocation -> {
57+
Object listener = invocation.getArgument(1);
58+
try {
59+
invokeProductDetailsResponse(listener, BillingClient.BillingResponseCode.OK, detailsList);
60+
} catch (Exception exception) {
61+
throw new RuntimeException(exception);
62+
}
63+
return null;
64+
}).when(billingClient).queryProductDetailsAsync(Mockito.any(), Mockito.any());
65+
66+
AtomicReference<List<ProductDetails>> captured = new AtomicReference<>();
67+
repository.queryProductDetails(List.of("donation"), captured::set);
68+
69+
assertEquals(detailsList, captured.get());
70+
71+
Map<String, ProductDetails> productDetailsMap = getProductDetailsMap(repository);
72+
assertSame(productDetails, productDetailsMap.get("donation"));
73+
}
74+
75+
@Test
76+
public void initiatePurchaseLaunchesFlowWithOfferToken() throws Exception {
77+
Map<String, ProductDetails> productDetailsMap = getProductDetailsMap(repository);
78+
79+
ProductDetails productDetails = Mockito.mock(ProductDetails.class);
80+
ProductDetails.OneTimePurchaseOfferDetails offerDetails = Mockito.mock(ProductDetails.OneTimePurchaseOfferDetails.class);
81+
Mockito.when(offerDetails.getOfferToken()).thenReturn("token-123");
82+
Mockito.when(productDetails.getOneTimePurchaseOfferDetails()).thenReturn(offerDetails);
83+
Mockito.when(productDetails.getProductId()).thenReturn("donation");
84+
productDetailsMap.put("donation", productDetails);
85+
86+
BillingResult launchResult = Mockito.mock(BillingResult.class);
87+
Mockito.when(billingClient.launchBillingFlow(Mockito.any(Activity.class), Mockito.any(BillingFlowParams.class)))
88+
.thenReturn(launchResult);
89+
90+
BillingFlowLauncher launcher = repository.initiatePurchase("donation");
91+
assertNotNull(launcher);
92+
93+
Activity activity = Mockito.mock(Activity.class);
94+
launcher.launch(activity);
95+
96+
ArgumentCaptor<BillingFlowParams> paramsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class);
97+
Mockito.verify(billingClient).launchBillingFlow(Mockito.eq(activity), paramsCaptor.capture());
98+
99+
BillingFlowParams flowParams = paramsCaptor.getValue();
100+
List<?> paramsList = getProductDetailsParamsList(flowParams);
101+
assertEquals(1, paramsList.size());
102+
Object params = paramsList.get(0);
103+
assertSame(productDetails, getProductDetailsFromParams(params));
104+
assertEquals("token-123", getOfferTokenFromParams(params));
105+
}
106+
107+
@Test
108+
public void initiatePurchaseReturnsNullWhenDetailsMissing() {
109+
BillingFlowLauncher launcher = repository.initiatePurchase("missing");
110+
assertNull(launcher);
111+
Mockito.verifyNoInteractions(billingClient);
112+
}
113+
114+
@Test
115+
public void initMobileAdsDelegatesToAdUtils() {
116+
try (MockedStatic<AdUtils> mockedAdUtils = Mockito.mockStatic(AdUtils.class)) {
117+
AdRequest request = repository.initMobileAds();
118+
119+
mockedAdUtils.verify(() -> AdUtils.initialize(context));
120+
assertNotNull(request);
121+
}
122+
}
123+
124+
private void invokeProductDetailsResponse(Object listener, int responseCode, List<ProductDetails> details)
125+
throws Exception {
126+
BillingResult billingResult = Mockito.mock(BillingResult.class);
127+
Mockito.when(billingResult.getResponseCode()).thenReturn(responseCode);
128+
129+
Method callbackMethod = findListenerMethod(listener);
130+
Class<?> resultType = callbackMethod.getParameterTypes()[1];
131+
Object resultArgument;
132+
if (List.class.isAssignableFrom(resultType)) {
133+
resultArgument = details;
134+
} else {
135+
resultArgument = Mockito.mock(resultType, invocation -> {
136+
if ("getProductDetailsList".equals(invocation.getMethod().getName())) {
137+
return details;
138+
}
139+
return null;
140+
});
141+
}
142+
callbackMethod.invoke(listener, billingResult, resultArgument);
143+
}
144+
145+
private Method findListenerMethod(Object listener) {
146+
for (Method method : listener.getClass().getDeclaredMethods()) {
147+
if (!method.isSynthetic()) {
148+
method.setAccessible(true);
149+
return method;
150+
}
151+
}
152+
throw new IllegalStateException("Unable to locate listener method");
153+
}
154+
155+
private Map<String, ProductDetails> getProductDetailsMap(DefaultSupportRepository target) throws Exception {
156+
Field field = DefaultSupportRepository.class.getDeclaredField("productDetailsMap");
157+
field.setAccessible(true);
158+
//noinspection unchecked
159+
return (Map<String, ProductDetails>) field.get(target);
160+
}
161+
162+
private void setBillingClient(DefaultSupportRepository target, BillingClient client) throws Exception {
163+
Field field = DefaultSupportRepository.class.getDeclaredField("billingClient");
164+
field.setAccessible(true);
165+
field.set(target, client);
166+
}
167+
168+
private List<?> getProductDetailsParamsList(BillingFlowParams params) throws Exception {
169+
Method method = BillingFlowParams.class.getDeclaredMethod("getProductDetailsParamsList");
170+
method.setAccessible(true);
171+
return (List<?>) method.invoke(params);
172+
}
173+
174+
private Object getProductDetailsFromParams(Object params) throws Exception {
175+
Method method = params.getClass().getDeclaredMethod("getProductDetails");
176+
method.setAccessible(true);
177+
return method.invoke(params);
178+
}
179+
180+
private String getOfferTokenFromParams(Object params) throws Exception {
181+
Method method = params.getClass().getDeclaredMethod("getOfferToken");
182+
method.setAccessible(true);
183+
Object token = method.invoke(params);
184+
return token != null ? token.toString() : null;
185+
}
186+
}

0 commit comments

Comments
 (0)