Skip to content

Commit 42f845c

Browse files
author
Mike Davis
committed
Refactor OptimizelyTest to have less reliance on Mockito. (#308)
1 parent 464370f commit 42f845c

File tree

11 files changed

+978
-2352
lines changed

11 files changed

+978
-2352
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,11 @@ public Builder withNotificationCenter(NotificationCenter notificationCenter) {
10681068
}
10691069

10701070
// Helper function for making testing easier
1071+
protected Builder withDatafile(String datafile) {
1072+
this.datafile = datafile;
1073+
return this;
1074+
}
1075+
10711076
protected Builder withBucketing(Bucketer bucketer) {
10721077
this.bucketer = bucketer;
10731078
return this;
@@ -1083,11 +1088,6 @@ protected Builder withDecisionService(DecisionService decisionService) {
10831088
return this;
10841089
}
10851090

1086-
protected Builder withEventBuilder(EventFactory eventFactory) {
1087-
this.eventFactory = eventFactory;
1088-
return this;
1089-
}
1090-
10911091
public Optimizely build() {
10921092

10931093
if (clientEngine == null) {

core-api/src/main/java/com/optimizely/ab/event/LogEvent.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.optimizely.ab.event.internal.serializer.Serializer;
2222

2323
import java.util.Map;
24+
import java.util.Objects;
2425

2526
import javax.annotation.Nonnull;
2627
import javax.annotation.concurrent.Immutable;
@@ -69,6 +70,10 @@ public String getBody() {
6970
return serializer.serialize(eventBatch);
7071
}
7172

73+
public EventBatch getEventBatch() {
74+
return eventBatch;
75+
}
76+
7277
//======== Overriding method ========//
7378

7479
@Override
@@ -81,6 +86,22 @@ public String toString() {
8186
'}';
8287
}
8388

89+
@Override
90+
public boolean equals(Object o) {
91+
if (this == o) return true;
92+
if (o == null || getClass() != o.getClass()) return false;
93+
LogEvent logEvent = (LogEvent) o;
94+
return requestMethod == logEvent.requestMethod &&
95+
Objects.equals(endpointUrl, logEvent.endpointUrl) &&
96+
Objects.equals(requestParams, logEvent.requestParams) &&
97+
Objects.equals(eventBatch, logEvent.eventBatch);
98+
}
99+
100+
@Override
101+
public int hashCode() {
102+
return Objects.hash(requestMethod, endpointUrl, requestParams, eventBatch);
103+
}
104+
84105
//======== Helper classes ========//
85106

86107
/**
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/**
2+
* Copyright 2019, Optimizely Inc. and contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.optimizely.ab;
17+
18+
import com.optimizely.ab.event.EventHandler;
19+
import com.optimizely.ab.event.LogEvent;
20+
import com.optimizely.ab.event.internal.payload.*;
21+
import org.junit.rules.TestRule;
22+
import org.junit.runner.Description;
23+
import org.junit.runners.model.Statement;
24+
25+
import java.util.*;
26+
import java.util.stream.Collectors;
27+
28+
import static com.optimizely.ab.config.ProjectConfig.RESERVED_ATTRIBUTE_PREFIX;
29+
import static junit.framework.TestCase.assertTrue;
30+
import static org.junit.Assert.assertEquals;
31+
import static org.junit.Assert.fail;
32+
33+
/**
34+
* EventHandlerRule is a JUnit rule that implements an Optimizely {@link EventHandler}.
35+
*
36+
* This implementation captures events being dispatched in a List.
37+
*
38+
* The List of "actual" events are compared, in order, against a list of "expected" events.
39+
*
40+
* Expected events are validated immediately against the head of actual events. If the queue is empty,
41+
* then a failure is raised. This is to make it easy to map back to the failing test line number.
42+
*
43+
* A failure is raised if at the end of the test there remain non-validated actual events. This is by design
44+
* to ensure that all outbound traffic is known and validated.
45+
*
46+
* TODO this rule does not yet support validation of event tags found in the {@link Event} payload.
47+
*/
48+
public class EventHandlerRule implements EventHandler, TestRule {
49+
50+
private static final String IMPRESSION_EVENT_NAME = "campaign_activated";
51+
52+
private LinkedList<CanonicalEvent> actualEvents;
53+
54+
@Override
55+
public Statement apply(final Statement base, Description description) {
56+
return new Statement() {
57+
@Override
58+
public void evaluate() throws Throwable {
59+
before();
60+
try {
61+
base.evaluate();
62+
verify();
63+
} finally {
64+
after();
65+
}
66+
}
67+
};
68+
}
69+
70+
private void before() {
71+
actualEvents = new LinkedList<>();
72+
}
73+
74+
private void after() {
75+
}
76+
77+
private void verify() {
78+
assertTrue(actualEvents.isEmpty());
79+
}
80+
81+
public void expectImpression(String experientId, String variationId, String userId) {
82+
expectImpression(experientId, variationId, userId, Collections.emptyMap());
83+
}
84+
85+
public void expectImpression(String experientId, String variationId, String userId, Map<String, ?> attributes) {
86+
verify(experientId, variationId, IMPRESSION_EVENT_NAME, userId, attributes, null);
87+
}
88+
89+
public void expectConversion(String eventName, String userId) {
90+
expectConversion(eventName, userId, Collections.emptyMap());
91+
}
92+
93+
public void expectConversion(String eventName, String userId, Map<String, ?> attributes) {
94+
expectConversion(eventName, userId, attributes, Collections.emptyMap());
95+
}
96+
97+
public void expectConversion(String eventName, String userId, Map<String, ?> attributes, Map<String, ?> tags) {
98+
verify(null, null, eventName, userId, attributes, tags);
99+
}
100+
101+
public void verify(String experientId, String variationId, String eventName, String userId,
102+
Map<String, ?> attributes, Map<String, ?> tags) {
103+
CanonicalEvent expectedEvent = new CanonicalEvent(experientId, variationId, eventName, userId, attributes, tags);
104+
verify(expectedEvent);
105+
}
106+
107+
public void verify(CanonicalEvent expected) {
108+
if (actualEvents.isEmpty()) {
109+
fail(String.format("Expected: %s, but not events are queued", expected));
110+
}
111+
112+
CanonicalEvent actual = actualEvents.removeFirst();
113+
assertEquals(expected, actual);
114+
}
115+
116+
@Override
117+
public void dispatchEvent(LogEvent logEvent) {
118+
List<Visitor> visitors = logEvent.getEventBatch().getVisitors();
119+
120+
if (visitors == null) {
121+
return;
122+
}
123+
124+
for (Visitor visitor: visitors) {
125+
for (Snapshot snapshot: visitor.getSnapshots()) {
126+
List<Decision> decisions = snapshot.getDecisions();
127+
if (decisions == null) {
128+
decisions = new ArrayList<>();
129+
}
130+
131+
if (decisions.isEmpty()) {
132+
decisions.add(new Decision());
133+
}
134+
135+
for (Decision decision: decisions) {
136+
for (Event event: snapshot.getEvents()) {
137+
CanonicalEvent actual = new CanonicalEvent(
138+
decision.getExperimentId(),
139+
decision.getVariationId(),
140+
event.getKey(),
141+
visitor.getVisitorId(),
142+
visitor.getAttributes().stream()
143+
.filter(attribute -> !attribute.getKey().startsWith(RESERVED_ATTRIBUTE_PREFIX))
144+
.collect(Collectors.toMap(Attribute::getKey, Attribute::getValue)),
145+
event.getTags()
146+
);
147+
148+
actualEvents.add(actual);
149+
}
150+
}
151+
}
152+
}
153+
}
154+
155+
private static class CanonicalEvent {
156+
private String experimentId;
157+
private String variationId;
158+
private String eventName;
159+
private String visitorId;
160+
private Map<String, ?> attributes;
161+
private Map<String, ?> tags;
162+
163+
public CanonicalEvent(String experimentId, String variationId, String eventName,
164+
String visitorId, Map<String, ?> attributes, Map<String, ?> tags) {
165+
this.experimentId = experimentId;
166+
this.variationId = variationId;
167+
this.eventName = eventName;
168+
this.visitorId = visitorId;
169+
this.attributes = attributes;
170+
this.tags = tags;
171+
}
172+
173+
@Override
174+
public boolean equals(Object o) {
175+
if (this == o) return true;
176+
if (o == null || getClass() != o.getClass()) return false;
177+
CanonicalEvent that = (CanonicalEvent) o;
178+
return Objects.equals(experimentId, that.experimentId) &&
179+
Objects.equals(variationId, that.variationId) &&
180+
Objects.equals(eventName, that.eventName) &&
181+
Objects.equals(visitorId, that.visitorId) &&
182+
Objects.equals(attributes, that.attributes) &&
183+
Objects.equals(tags, that.tags);
184+
}
185+
186+
@Override
187+
public int hashCode() {
188+
return Objects.hash(experimentId, variationId, eventName, visitorId, attributes, tags);
189+
}
190+
191+
@Override
192+
public String toString() {
193+
return new StringJoiner(", ", CanonicalEvent.class.getSimpleName() + "[", "]")
194+
.add("experimentId='" + experimentId + "'")
195+
.add("variationId='" + variationId + "'")
196+
.add("eventName='" + eventName + "'")
197+
.add("visitorId='" + visitorId + "'")
198+
.add("attributes=" + attributes)
199+
.add("tags=" + tags)
200+
.toString();
201+
}
202+
}
203+
}

0 commit comments

Comments
 (0)