Skip to content

Commit b0a0881

Browse files
committed
test without mockito
1 parent 7eb1177 commit b0a0881

2 files changed

Lines changed: 446 additions & 194 deletions

File tree

Lines changed: 223 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
package server;
22

3-
import static org.mockito.ArgumentMatchers.any;
4-
import static org.mockito.ArgumentMatchers.eq;
5-
import static org.mockito.Mockito.mock;
6-
import static org.mockito.Mockito.never;
7-
import static org.mockito.Mockito.times;
8-
import static org.mockito.Mockito.verify;
9-
import static org.mockito.Mockito.verifyNoMoreInteractions;
10-
import static org.mockito.Mockito.when;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
114

125
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
136
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
@@ -16,108 +9,241 @@
169
import io.vertx.core.http.HttpServerRequest;
1710
import io.vertx.ext.web.Route;
1811
import io.vertx.ext.web.RoutingContext;
12+
import java.lang.reflect.InvocationHandler;
13+
import java.lang.reflect.Method;
14+
import java.lang.reflect.Proxy;
15+
import java.util.ArrayList;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
1919
import org.junit.jupiter.api.Test;
2020

2121
class RouteHandlerWrapperTest {
2222

2323
@Test
2424
void updateRouteWritesRouteToBothSpans() {
25-
RoutingContext context = mock(RoutingContext.class);
26-
HttpServerRequest request = mock(HttpServerRequest.class);
27-
Route route = mock(Route.class);
28-
AgentSpan parentSpan = mock(AgentSpan.class);
29-
AgentSpan handlerSpan = mock(AgentSpan.class);
30-
when(route.getPath()).thenReturn("/items/:id");
31-
when(context.mountPoint()).thenReturn(null);
32-
when(context.request()).thenReturn(request);
33-
when(request.path()).thenReturn("/items/123");
34-
when(request.rawMethod()).thenReturn("GET");
35-
when(context.get("dd." + Tags.HTTP_ROUTE)).thenReturn(null);
36-
when(handlerSpan.getSpanName()).thenReturn("vertx.route-handler");
37-
when(handlerSpan.getResourceNamePriority()).thenReturn(Byte.MIN_VALUE);
38-
39-
RouteUpdateHelper.updateRouteFromMatchedRoute(context, route, parentSpan, handlerSpan);
40-
41-
verify(route).getPath();
42-
verify(context).mountPoint();
43-
verify(context, times(2)).request();
44-
verify(request).path();
45-
verify(request).rawMethod();
46-
verify(context).get("dd." + Tags.HTTP_ROUTE);
47-
verify(context).put("dd.vertx.matched_route", "/items/:id");
48-
verify(context).put("dd." + Tags.HTTP_ROUTE, "/items/:id");
49-
verify(parentSpan).setTag(Tags.HTTP_ROUTE, (CharSequence) "/items/:id");
50-
verify(parentSpan)
51-
.setResourceName(any(CharSequence.class), eq(ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE));
52-
verify(handlerSpan).getSpanName();
53-
verify(handlerSpan).getResourceNamePriority();
54-
verify(handlerSpan).setTag(Tags.HTTP_ROUTE, (CharSequence) "/items/:id");
55-
verify(handlerSpan)
56-
.setResourceName(any(CharSequence.class), eq(ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE));
57-
verifyNoMoreInteractions(context, request, route, parentSpan, handlerSpan);
25+
RecordingProxy<RoutingContext> context = RecordingProxy.of(RoutingContext.class);
26+
RecordingProxy<HttpServerRequest> request = RecordingProxy.of(HttpServerRequest.class);
27+
RecordingProxy<Route> route = RecordingProxy.of(Route.class);
28+
RecordingProxy<AgentSpan> parentSpan = RecordingProxy.of(AgentSpan.class);
29+
RecordingProxy<AgentSpan> handlerSpan = RecordingProxy.of(AgentSpan.class);
30+
route.returns("getPath", "/items/:id");
31+
context.returns("mountPoint", null);
32+
context.returns("request", request.instance);
33+
request.returns("path", "/items/123");
34+
request.returns("rawMethod", "GET");
35+
context.returns("get", null);
36+
handlerSpan.returns("getSpanName", "vertx.route-handler");
37+
handlerSpan.returns("getResourceNamePriority", Byte.MIN_VALUE);
38+
39+
RouteUpdateHelper.updateRouteFromMatchedRoute(
40+
context.instance, route.instance, parentSpan.instance, handlerSpan.instance);
41+
42+
assertEquals(1, route.count("getPath"));
43+
assertEquals(1, context.count("mountPoint"));
44+
assertEquals(2, context.count("request"));
45+
assertEquals(1, request.count("path"));
46+
assertEquals(1, request.count("rawMethod"));
47+
assertEquals(1, context.count("get", "dd." + Tags.HTTP_ROUTE));
48+
assertEquals(1, context.count("put", "dd.vertx.matched_route", "/items/:id"));
49+
assertEquals(1, context.count("put", "dd." + Tags.HTTP_ROUTE, "/items/:id"));
50+
assertEquals(1, parentSpan.count("setTag", Tags.HTTP_ROUTE, "/items/:id"));
51+
assertEquals(
52+
1,
53+
parentSpan.count(
54+
"setResourceName", "GET /items/:id", ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE));
55+
assertEquals(1, handlerSpan.count("getSpanName"));
56+
assertEquals(1, handlerSpan.count("getResourceNamePriority"));
57+
assertEquals(1, handlerSpan.count("setTag", Tags.HTTP_ROUTE, "/items/:id"));
58+
assertEquals(
59+
1,
60+
handlerSpan.count(
61+
"setResourceName", "GET /items/:id", ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE));
5862
}
5963

6064
@Test
6165
void updateRouteDoesNotWriteRouteToNonVertxHandlerSpan() {
62-
RoutingContext context = mock(RoutingContext.class);
63-
HttpServerRequest request = mock(HttpServerRequest.class);
64-
Route route = mock(Route.class);
65-
AgentSpan parentSpan = mock(AgentSpan.class);
66-
AgentSpan handlerSpan = mock(AgentSpan.class);
67-
when(route.getPath()).thenReturn("/items/:id");
68-
when(context.mountPoint()).thenReturn(null);
69-
when(context.request()).thenReturn(request);
70-
when(request.path()).thenReturn("/items/123");
71-
when(request.rawMethod()).thenReturn("GET");
72-
when(context.get("dd." + Tags.HTTP_ROUTE)).thenReturn(null);
73-
when(handlerSpan.getSpanName()).thenReturn("some.other.span");
74-
75-
RouteUpdateHelper.updateRouteFromMatchedRoute(context, route, parentSpan, handlerSpan);
76-
77-
verify(route).getPath();
78-
verify(context).mountPoint();
79-
verify(context, times(2)).request();
80-
verify(request).path();
81-
verify(request).rawMethod();
82-
verify(context).get("dd." + Tags.HTTP_ROUTE);
83-
verify(context).put("dd.vertx.matched_route", "/items/:id");
84-
verify(context).put("dd." + Tags.HTTP_ROUTE, "/items/:id");
85-
verify(parentSpan).setTag(Tags.HTTP_ROUTE, (CharSequence) "/items/:id");
86-
verify(parentSpan)
87-
.setResourceName(any(CharSequence.class), eq(ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE));
88-
verify(handlerSpan).getSpanName();
89-
verify(handlerSpan, never()).setTag(any(String.class), any(CharSequence.class));
90-
verifyNoMoreInteractions(context, request, route, parentSpan, handlerSpan);
66+
RecordingProxy<RoutingContext> context = RecordingProxy.of(RoutingContext.class);
67+
RecordingProxy<HttpServerRequest> request = RecordingProxy.of(HttpServerRequest.class);
68+
RecordingProxy<Route> route = RecordingProxy.of(Route.class);
69+
RecordingProxy<AgentSpan> parentSpan = RecordingProxy.of(AgentSpan.class);
70+
RecordingProxy<AgentSpan> handlerSpan = RecordingProxy.of(AgentSpan.class);
71+
route.returns("getPath", "/items/:id");
72+
context.returns("mountPoint", null);
73+
context.returns("request", request.instance);
74+
request.returns("path", "/items/123");
75+
request.returns("rawMethod", "GET");
76+
context.returns("get", null);
77+
handlerSpan.returns("getSpanName", "some.other.span");
78+
79+
RouteUpdateHelper.updateRouteFromMatchedRoute(
80+
context.instance, route.instance, parentSpan.instance, handlerSpan.instance);
81+
82+
assertEquals(1, route.count("getPath"));
83+
assertEquals(1, context.count("mountPoint"));
84+
assertEquals(2, context.count("request"));
85+
assertEquals(1, request.count("path"));
86+
assertEquals(1, request.count("rawMethod"));
87+
assertEquals(1, context.count("get", "dd." + Tags.HTTP_ROUTE));
88+
assertEquals(1, context.count("put", "dd.vertx.matched_route", "/items/:id"));
89+
assertEquals(1, context.count("put", "dd." + Tags.HTTP_ROUTE, "/items/:id"));
90+
assertEquals(1, parentSpan.count("setTag", Tags.HTTP_ROUTE, "/items/:id"));
91+
assertEquals(
92+
1,
93+
parentSpan.count(
94+
"setResourceName", "GET /items/:id", ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE));
95+
assertEquals(1, handlerSpan.count("getSpanName"));
96+
assertEquals(0, handlerSpan.count("setTag"));
97+
assertEquals(0, handlerSpan.count("setResourceName"));
9198
}
9299

93100
@Test
94101
void updateRouteDoesNotReplaceRootRouteWhenOneExists() {
95-
RoutingContext context = mock(RoutingContext.class);
96-
HttpServerRequest request = mock(HttpServerRequest.class);
97-
Route route = mock(Route.class);
98-
AgentSpan parentSpan = mock(AgentSpan.class);
99-
AgentSpan handlerSpan = mock(AgentSpan.class);
100-
when(route.getPath()).thenReturn("/");
101-
when(context.mountPoint()).thenReturn(null);
102-
when(context.request()).thenReturn(request);
103-
when(request.path()).thenReturn("/");
104-
when(request.rawMethod()).thenReturn("GET");
105-
when(context.get("dd." + Tags.HTTP_ROUTE)).thenReturn(null);
106-
when(parentSpan.getTag(Tags.HTTP_ROUTE)).thenReturn("/existing");
107-
108-
RouteUpdateHelper.updateRouteFromMatchedRoute(context, route, parentSpan, handlerSpan);
109-
110-
verify(route).getPath();
111-
verify(context).mountPoint();
112-
verify(context, times(2)).request();
113-
verify(request).path();
114-
verify(request).rawMethod();
115-
verify(context).get("dd." + Tags.HTTP_ROUTE);
116-
verify(context).put("dd.vertx.matched_route", "/");
117-
verify(parentSpan).getTag(Tags.HTTP_ROUTE);
118-
verify(context, never()).put("dd." + Tags.HTTP_ROUTE, "/");
119-
verify(parentSpan, never()).setTag(any(String.class), any(CharSequence.class));
120-
verify(handlerSpan, never()).setTag(any(String.class), any(CharSequence.class));
121-
verifyNoMoreInteractions(context, request, route, parentSpan, handlerSpan);
102+
RecordingProxy<RoutingContext> context = RecordingProxy.of(RoutingContext.class);
103+
RecordingProxy<HttpServerRequest> request = RecordingProxy.of(HttpServerRequest.class);
104+
RecordingProxy<Route> route = RecordingProxy.of(Route.class);
105+
RecordingProxy<AgentSpan> parentSpan = RecordingProxy.of(AgentSpan.class);
106+
RecordingProxy<AgentSpan> handlerSpan = RecordingProxy.of(AgentSpan.class);
107+
route.returns("getPath", "/");
108+
context.returns("mountPoint", null);
109+
context.returns("request", request.instance);
110+
request.returns("path", "/");
111+
request.returns("rawMethod", "GET");
112+
context.returns("get", null);
113+
parentSpan.returns("getTag", "/existing");
114+
115+
RouteUpdateHelper.updateRouteFromMatchedRoute(
116+
context.instance, route.instance, parentSpan.instance, handlerSpan.instance);
117+
118+
assertEquals(1, route.count("getPath"));
119+
assertEquals(1, context.count("mountPoint"));
120+
assertEquals(2, context.count("request"));
121+
assertEquals(1, request.count("path"));
122+
assertEquals(1, request.count("rawMethod"));
123+
assertEquals(1, context.count("get", "dd." + Tags.HTTP_ROUTE));
124+
assertEquals(1, context.count("put", "dd.vertx.matched_route", "/"));
125+
assertEquals(1, parentSpan.count("getTag", Tags.HTTP_ROUTE));
126+
assertEquals(0, context.count("put", "dd." + Tags.HTTP_ROUTE, "/"));
127+
assertEquals(0, parentSpan.count("setTag"));
128+
assertEquals(0, handlerSpan.count("setTag"));
129+
}
130+
131+
private static final class RecordingProxy<T> implements InvocationHandler {
132+
private final Map<String, Object> returnValues = new HashMap<>();
133+
private final List<Call> calls = new ArrayList<>();
134+
private final T instance;
135+
136+
private RecordingProxy(Class<T> type) {
137+
this.instance =
138+
type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] {type}, this));
139+
}
140+
141+
static <T> RecordingProxy<T> of(Class<T> type) {
142+
return new RecordingProxy<T>(type);
143+
}
144+
145+
void returns(String method, Object value) {
146+
returnValues.put(method, value);
147+
}
148+
149+
int count(String method, Object... args) {
150+
int count = 0;
151+
for (Call call : calls) {
152+
if (call.matches(method, args)) {
153+
count++;
154+
}
155+
}
156+
return count;
157+
}
158+
159+
@Override
160+
public Object invoke(Object proxy, Method method, Object[] args) {
161+
if (method.getDeclaringClass() == Object.class) {
162+
return invokeObjectMethod(proxy, method, args);
163+
}
164+
Object[] arguments = args == null ? new Object[0] : args;
165+
calls.add(new Call(method.getName(), arguments));
166+
if (returnValues.containsKey(method.getName())) {
167+
return returnValues.get(method.getName());
168+
}
169+
Class<?> returnType = method.getReturnType();
170+
if (returnType.isInstance(proxy)) {
171+
return proxy;
172+
}
173+
return defaultValue(returnType);
174+
}
175+
176+
private static Object invokeObjectMethod(Object proxy, Method method, Object[] args) {
177+
if ("toString".equals(method.getName())) {
178+
return proxy.getClass().getInterfaces()[0].getName() + " proxy";
179+
}
180+
if ("hashCode".equals(method.getName())) {
181+
return System.identityHashCode(proxy);
182+
}
183+
if ("equals".equals(method.getName())) {
184+
return proxy == args[0];
185+
}
186+
throw new UnsupportedOperationException(method.getName());
187+
}
188+
189+
private static Object defaultValue(Class<?> type) {
190+
if (!type.isPrimitive() || Void.TYPE == type) {
191+
return null;
192+
}
193+
if (Boolean.TYPE == type) {
194+
return false;
195+
}
196+
if (Byte.TYPE == type) {
197+
return (byte) 0;
198+
}
199+
if (Short.TYPE == type) {
200+
return (short) 0;
201+
}
202+
if (Integer.TYPE == type) {
203+
return 0;
204+
}
205+
if (Long.TYPE == type) {
206+
return 0L;
207+
}
208+
if (Float.TYPE == type) {
209+
return 0F;
210+
}
211+
if (Double.TYPE == type) {
212+
return 0D;
213+
}
214+
if (Character.TYPE == type) {
215+
return (char) 0;
216+
}
217+
return null;
218+
}
219+
}
220+
221+
private static final class Call {
222+
private final String method;
223+
private final Object[] args;
224+
225+
private Call(String method, Object[] args) {
226+
this.method = method;
227+
this.args = args;
228+
}
229+
230+
private boolean matches(String method, Object[] args) {
231+
if (!this.method.equals(method) || this.args.length != args.length) {
232+
return false;
233+
}
234+
for (int i = 0; i < args.length; i++) {
235+
if (!argumentMatches(this.args[i], args[i])) {
236+
return false;
237+
}
238+
}
239+
return true;
240+
}
241+
242+
private static boolean argumentMatches(Object actual, Object expected) {
243+
if (actual instanceof CharSequence && expected instanceof CharSequence) {
244+
return actual.toString().contentEquals((CharSequence) expected);
245+
}
246+
return java.util.Objects.equals(actual, expected);
247+
}
122248
}
123249
}

0 commit comments

Comments
 (0)