Skip to content

Commit 606f82c

Browse files
authored
Merge pull request #15 from optimizely/on-start-callback-tweaks
Tweaks on start callback so that it is only hit once
2 parents efe3763 + c4fbb64 commit 606f82c

File tree

2 files changed

+94
-30
lines changed

2 files changed

+94
-30
lines changed

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package com.optimizely.ab.android.sdk;
1717

18-
import android.app.Activity;
1918
import android.content.Context;
2019
import android.content.Intent;
2120
import android.support.test.espresso.core.deps.guava.util.concurrent.ListeningExecutorService;
@@ -26,7 +25,6 @@
2625
import com.optimizely.user_experiment_record.AndroidUserExperimentRecord;
2726

2827
import org.junit.Before;
29-
import org.junit.Ignore;
3028
import org.junit.Test;
3129
import org.junit.runner.RunWith;
3230
import org.mockito.ArgumentCaptor;
@@ -42,20 +40,13 @@
4240
import static org.mockito.Matchers.any;
4341
import static org.mockito.Matchers.eq;
4442
import static org.mockito.Mockito.mock;
45-
import static org.mockito.Mockito.never;
4643
import static org.mockito.Mockito.verify;
4744
import static org.mockito.Mockito.when;
4845

4946
/**
5047
* Created by jdeffibaugh on 8/3/16 for Optimizely.
5148
*
5249
* Tests for {@link OptimizelyManager}
53-
*
54-
* *NOTE*
55-
* Some tests are ignored here because Activity#getApplication() is final and can't be mocked
56-
* Also, mockito fails when making {@link OptimizelyManager#stop(Activity, OptimizelyManager.OptlyActivityLifecycleCallbacks)}
57-
* private or package private.
58-
* // TODO Get these tests working via PowerMock https://github.com/jayway/powermock
5950
*/
6051
@RunWith(AndroidJUnit4.class)
6152
public class OptimizelyManagerTest {
@@ -65,6 +56,18 @@ public class OptimizelyManagerTest {
6556
Logger logger;
6657
OptimizelyManager optimizelyManager;
6758

59+
String minDataFile = "{\n" +
60+
"experiments: [ ],\n" +
61+
"version: \"2\",\n" +
62+
"audiences: [ ],\n" +
63+
"groups: [ ],\n" +
64+
"attributes: [ ],\n" +
65+
"projectId: \"7595190003\",\n" +
66+
"accountId: \"6365361536\",\n" +
67+
"events: [ ],\n" +
68+
"revision: \"1\"\n" +
69+
"}";
70+
6871
@Before
6972
public void setup() {
7073
logger = mock(Logger.class);
@@ -76,61 +79,58 @@ public void setup() {
7679

7780
@SuppressWarnings("WrongConstant")
7881
@Test
79-
@Ignore
8082
public void start() {
8183
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
82-
Activity activity = mock(Activity.class);
8384
Context context = mock(Context.class);
84-
when(context.getPackageName()).thenReturn("com.optly");
85+
Context appContext = mock(Context.class);
86+
when(context.getApplicationContext()).thenReturn(appContext);
87+
when(appContext.getPackageName()).thenReturn("com.optly");
8588
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
8689

87-
optimizelyManager.start(activity, startListener);
90+
optimizelyManager.start(context, startListener);
8891

8992
assertNotNull(optimizelyManager.getOptimizelyStartListener());
9093
assertNotNull(optimizelyManager.getDataFileServiceConnection());
9194

92-
verify(context).bindService(captor.capture(), any(OptimizelyManager.DataFileServiceConnection.class), eq(Context.BIND_AUTO_CREATE));
95+
verify(appContext).bindService(captor.capture(), any(OptimizelyManager.DataFileServiceConnection.class), eq(Context.BIND_AUTO_CREATE));
9396

9497
Intent intent = captor.getValue();
9598
assertTrue(intent.getComponent().getShortClassName().contains("DataFileService"));
9699
}
97100

98101
@Test
99-
@Ignore
100102
public void stop() {
101103
Context context = mock(Context.class);
102-
Activity activity = mock(Activity.class);
103-
OptimizelyManager.OptlyActivityLifecycleCallbacks activityLifecycleCallbacks = mock(OptimizelyManager.OptlyActivityLifecycleCallbacks.class);
104+
Context appContext = mock(Context.class);
105+
when(context.getApplicationContext()).thenReturn(appContext);
104106

105107
OptimizelyManager.DataFileServiceConnection dataFileServiceConnection = mock(OptimizelyManager.DataFileServiceConnection.class);
106108
optimizelyManager.setDataFileServiceConnection(dataFileServiceConnection);
107109
when(dataFileServiceConnection.isBound()).thenReturn(true);
108110

109-
optimizelyManager.stop(activity, activityLifecycleCallbacks);
111+
optimizelyManager.stop(context);
110112

111113
assertNull(optimizelyManager.getOptimizelyStartListener());
112-
verify(context).unbindService(dataFileServiceConnection);
114+
verify(appContext).unbindService(dataFileServiceConnection);
113115
}
114116

115-
// TODO add a data file fixture so parsing doesn't fail in SST core
116117
@Test
117-
@Ignore
118118
public void injectOptimizely() {
119119
Context context = mock(Context.class);
120120
AndroidUserExperimentRecord userExperimentRecord = mock(AndroidUserExperimentRecord.class);
121121
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
122122
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
123123
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
124124
optimizelyManager.setOptimizelyStartListener(startListener);
125-
optimizelyManager.injectOptimizely(context, userExperimentRecord, serviceScheduler, "");
125+
optimizelyManager.injectOptimizely(context, userExperimentRecord, serviceScheduler, minDataFile);
126126
try {
127127
executor.awaitTermination(5, TimeUnit.SECONDS);
128128
} catch (InterruptedException e) {
129129
fail("Timed out");
130130
}
131131

132132
verify(userExperimentRecord).start();
133-
verify(serviceScheduler).schedule(captor.capture(), TimeUnit.HOURS.toMillis(1L));
133+
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
134134
verify(logger).info("Sending Optimizely instance to listener");
135135
verify(startListener).onStart(any(AndroidOptimizely.class));
136136
}
@@ -142,9 +142,8 @@ public void injectOptimizelyNullListener() {
142142
AndroidUserExperimentRecord userExperimentRecord = mock(AndroidUserExperimentRecord.class);
143143
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
144144
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
145-
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
146145
optimizelyManager.setOptimizelyStartListener(null);
147-
optimizelyManager.injectOptimizely(context, userExperimentRecord, serviceScheduler, "");
146+
optimizelyManager.injectOptimizely(context, userExperimentRecord, serviceScheduler, minDataFile);
148147
try {
149148
executor.awaitTermination(5, TimeUnit.SECONDS);
150149
} catch (InterruptedException e) {
@@ -154,10 +153,65 @@ public void injectOptimizelyNullListener() {
154153
verify(userExperimentRecord).start();
155154
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
156155
verify(logger).info("No listener to send Optimizely to");
157-
verify(startListener, never()).onStart(any(AndroidOptimizely.class));
158156

159157
Intent intent = captor.getValue();
160158
assertTrue(intent.getComponent().getShortClassName().contains("DataFileService"));
161159
assertEquals(optimizelyManager.getProjectId(), intent.getStringExtra(DataFileService.EXTRA_PROJECT_ID));
162160
}
161+
162+
@Test
163+
public void injectOptimizelyHandlesInvalidDataFile() {
164+
Context context = mock(Context.class);
165+
when(context.getPackageName()).thenReturn("com.optly");
166+
AndroidUserExperimentRecord userExperimentRecord = mock(AndroidUserExperimentRecord.class);
167+
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
168+
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
169+
optimizelyManager.setOptimizelyStartListener(null);
170+
optimizelyManager.injectOptimizely(context, userExperimentRecord, serviceScheduler, "{}");
171+
try {
172+
executor.awaitTermination(5, TimeUnit.SECONDS);
173+
} catch (InterruptedException e) {
174+
fail("Timed out");
175+
}
176+
177+
verify(userExperimentRecord).start();
178+
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
179+
verify(logger).error(eq("Unable to build optimizely instance"), any(Exception.class));
180+
181+
Intent intent = captor.getValue();
182+
assertTrue(intent.getComponent().getShortClassName().contains("DataFileService"));
183+
assertEquals(optimizelyManager.getProjectId(), intent.getStringExtra(DataFileService.EXTRA_PROJECT_ID));
184+
}
185+
186+
@Test
187+
public void injectOptimizelyDoesNotDuplicateCallback() {
188+
Context context = mock(Context.class);
189+
when(context.getPackageName()).thenReturn("com.optly");
190+
AndroidUserExperimentRecord userExperimentRecord = mock(AndroidUserExperimentRecord.class);
191+
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
192+
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
193+
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
194+
optimizelyManager.setOptimizelyStartListener(startListener);
195+
optimizelyManager.injectOptimizely(context, userExperimentRecord, serviceScheduler, minDataFile);
196+
try {
197+
executor.awaitTermination(5, TimeUnit.SECONDS);
198+
} catch (InterruptedException e) {
199+
fail("Timed out");
200+
}
201+
202+
verify(userExperimentRecord).start();
203+
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
204+
205+
verify(logger).info("Sending Optimizely instance to listener");
206+
verify(startListener).onStart(any(AndroidOptimizely.class));
207+
208+
optimizelyManager.injectOptimizely(context, userExperimentRecord, serviceScheduler, minDataFile);
209+
try {
210+
executor.awaitTermination(5, TimeUnit.SECONDS);
211+
} catch (InterruptedException e) {
212+
fail("Timed out");
213+
}
214+
215+
verify(logger).info("No listener to send Optimizely to");
216+
}
163217
}

android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.optimizely.ab.android.event_handler.OptlyEventHandler;
3333
import com.optimizely.ab.android.shared.ServiceScheduler;
3434
import com.optimizely.ab.bucketing.UserExperimentRecord;
35+
import com.optimizely.ab.config.parser.ConfigParseException;
3536
import com.optimizely.user_experiment_record.AndroidUserExperimentRecord;
3637

3738
import org.slf4j.Logger;
@@ -146,18 +147,27 @@ protected void onPostExecute(UserExperimentRecord userExperimentRecord) {
146147
intent.putExtra(DataFileService.EXTRA_PROJECT_ID, projectId);
147148
serviceScheduler.schedule(intent, dataFileDownloadIntervalTimeUnit.toMillis(dataFileDownloadInterval));
148149

149-
if (optimizelyStartListener != null) {
150+
try {
150151
OptlyEventHandler eventHandler = OptlyEventHandler.getInstance(context);
151152
eventHandler.setDispatchInterval(eventHandlerDispatchInterval, eventHandlerDispatchIntervalTimeUnit);
152153
Optimizely optimizely = Optimizely.builder(dataFile, eventHandler)
153154
.withUserExperimentRecord(userExperimentRecord)
154155
.build();
155156
logger.info("Sending Optimizely instance to listener");
156157
AndroidOptimizely androidOptimizely = new AndroidOptimizely(optimizely);
157-
optimizelyStartListener.onStart(androidOptimizely);
158158
OptimizelyManager.androidOptimizely = androidOptimizely;
159-
} else {
160-
logger.info("No listener to send Optimizely to");
159+
160+
if (optimizelyStartListener != null) {
161+
optimizelyStartListener.onStart(androidOptimizely);
162+
// Prevent the onOptimizelyStarted(AndroidOptimizely) callback from being hit twice
163+
// This could happen if the local data file is not null and is different
164+
// from the remote data file. Setting the listener to null handles this case.
165+
optimizelyStartListener = null;
166+
} else {
167+
logger.info("No listener to send Optimizely to");
168+
}
169+
} catch (Exception e) {
170+
logger.error("Unable to build optimizely instance", e);
161171
}
162172
}
163173
};

0 commit comments

Comments
 (0)