Skip to content

Commit 8ed3364

Browse files
committed
MockRouter(unit test): support coroutine execution fix jooby-project#1681
1 parent eec4497 commit 8ed3364

File tree

5 files changed

+190
-4
lines changed

5 files changed

+190
-4
lines changed

jooby/src/main/kotlin/io/jooby/CoroutineRouter.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,6 @@ class CoroutineRouter(val coroutineStart: CoroutineStart, val router: Router) {
7474
}
7575
}
7676
}.setHandle(handler)
77+
.attribute("coroutine", true)
7778
}
7879
}

modules/jooby-test/pom.xml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,92 @@
9999
<version>${jooby.version}</version>
100100
<scope>test</scope>
101101
</dependency>
102+
103+
<!-- kotlin -->
104+
<dependency>
105+
<groupId>org.jetbrains.kotlin</groupId>
106+
<artifactId>kotlin-stdlib-jdk8</artifactId>
107+
<scope>test</scope>
108+
</dependency>
109+
110+
<dependency>
111+
<groupId>org.jetbrains.kotlin</groupId>
112+
<artifactId>kotlin-reflect</artifactId>
113+
<scope>test</scope>
114+
</dependency>
115+
116+
<dependency>
117+
<groupId>org.jetbrains.kotlinx</groupId>
118+
<artifactId>kotlinx-coroutines-core</artifactId>
119+
<scope>test</scope>
120+
</dependency>
102121
</dependencies>
122+
123+
<build>
124+
<plugins>
125+
<plugin>
126+
<artifactId>kotlin-maven-plugin</artifactId>
127+
<groupId>org.jetbrains.kotlin</groupId>
128+
<executions>
129+
<execution>
130+
<id>compile</id>
131+
<goals>
132+
<goal>compile</goal>
133+
</goals>
134+
<configuration>
135+
<sourceDirs>
136+
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
137+
<sourceDir>${project.basedir}/src/main/java</sourceDir>
138+
</sourceDirs>
139+
<jvmTarget>1.8</jvmTarget>
140+
</configuration>
141+
</execution>
142+
<execution>
143+
<id>test-compile</id>
144+
<goals>
145+
<goal>test-compile</goal>
146+
</goals>
147+
<configuration>
148+
<sourceDirs>
149+
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
150+
<sourceDir>${project.basedir}/src/test/java</sourceDir>
151+
</sourceDirs>
152+
</configuration>
153+
</execution>
154+
</executions>
155+
</plugin>
156+
157+
<plugin>
158+
<groupId>org.apache.maven.plugins</groupId>
159+
<artifactId>maven-compiler-plugin</artifactId>
160+
<configuration>
161+
<proc>none</proc>
162+
</configuration>
163+
<executions>
164+
<execution>
165+
<id>default-compile</id>
166+
<phase>none</phase>
167+
</execution>
168+
<execution>
169+
<id>default-testCompile</id>
170+
<phase>none</phase>
171+
</execution>
172+
<execution>
173+
<id>java-compile</id>
174+
<phase>compile</phase>
175+
<goals>
176+
<goal>compile</goal>
177+
</goals>
178+
</execution>
179+
<execution>
180+
<id>java-test-compile</id>
181+
<phase>test-compile</phase>
182+
<goals>
183+
<goal>testCompile</goal>
184+
</goals>
185+
</execution>
186+
</executions>
187+
</plugin>
188+
</plugins>
189+
</build>
103190
</project>

modules/jooby-test/src/main/java/io/jooby/MockResponse.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Collections;
1111
import java.util.Map;
1212
import java.util.TreeMap;
13+
import java.util.concurrent.CountDownLatch;
1314

1415
/**
1516
* Response generate by {@link MockRouter}. Contains all response metadata as well as route returns
@@ -50,6 +51,8 @@ public class MockResponse implements MockValue {
5051

5152
private long length = -1;
5253

54+
private CountDownLatch latch = new CountDownLatch(1);
55+
5356
/**
5457
* Response headers.
5558
*
@@ -172,6 +175,11 @@ public long getContentLength() {
172175
*/
173176
public @Nonnull MockResponse setResult(@Nullable Object result) {
174177
this.result = result;
178+
latch.countDown();
175179
return this;
176180
}
181+
182+
CountDownLatch getLatch() {
183+
return latch;
184+
}
177185
}

modules/jooby-test/src/main/java/io/jooby/MockRouter.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import javax.annotation.Nonnull;
99
import java.util.Optional;
10+
import java.util.concurrent.Executor;
11+
import java.util.concurrent.Executors;
1012
import java.util.function.Consumer;
1113
import java.util.function.Supplier;
1214

@@ -49,6 +51,8 @@ private static class SingleMockValue implements MockValue {
4951
private static final Consumer NOOP = value -> {
5052
};
5153

54+
private Executor worker;
55+
5256
private Supplier<Jooby> supplier;
5357

5458
private boolean fullExecution;
@@ -76,6 +80,26 @@ public MockRouter(@Nonnull Jooby application) {
7680
return this;
7781
}
7882

83+
/**
84+
* Get the worker executor for running test.
85+
*
86+
* @return Worker executor or <code>null</code>.
87+
*/
88+
public Executor getWorker() {
89+
return worker;
90+
}
91+
92+
/**
93+
* Set the worker executor to use.
94+
*
95+
* @param worker Worker executor.
96+
* @return This router.
97+
*/
98+
public MockRouter setWorker(@Nonnull Executor worker) {
99+
this.worker = worker;
100+
return this;
101+
}
102+
79103
/**
80104
* Execute a GET request to the target application.
81105
*
@@ -416,6 +440,10 @@ private MockValue call(Jooby router, String method, String path, Context ctx,
416440

417441
Router.Match match = router.match(findContext);
418442
Route route = match.route();
443+
boolean isCoroutine = route.attribute("coroutine") == Boolean.TRUE;
444+
if (isCoroutine) {
445+
router.setWorker(Optional.ofNullable(getWorker()).orElseGet(MockRouter::singleThreadWorker));
446+
}
419447
findContext.setPathMap(match.pathMap());
420448
findContext.setRoute(route);
421449
Object value;
@@ -429,14 +457,21 @@ private MockValue call(Jooby router, String method, String path, Context ctx,
429457
value = handler.apply(ctx);
430458
if (ctx instanceof MockContext) {
431459
MockResponse response = ((MockContext) ctx).getResponse();
432-
if (value != null && !(value instanceof Context)) {
433-
response.setResult(value);
460+
Object responseValue;
461+
if (isCoroutine) {
462+
response.getLatch().await();
463+
responseValue = response.value();
464+
} else {
465+
if (value != null && !(value instanceof Context)) {
466+
response.setResult(value);
467+
}
468+
responseValue = Optional.ofNullable(response.value()).orElse(value);
434469
}
435470
if (response.getContentLength() <= 0) {
436-
response.setContentLength(contentLength(value));
471+
response.setContentLength(contentLength(responseValue));
437472
}
438473
consumer.accept(response);
439-
return new SingleMockValue(Optional.ofNullable(response.value()).orElse(value));
474+
return new SingleMockValue(responseValue);
440475
}
441476
return new SingleMockValue(value);
442477
}
@@ -454,4 +489,12 @@ private long contentLength(Object value) {
454489
}
455490
return -1;
456491
}
492+
493+
private static Executor singleThreadWorker() {
494+
return Executors.newSingleThreadExecutor(task -> {
495+
Thread thread = new Thread(task, "single-thread");
496+
thread.setDaemon(true);
497+
return thread;
498+
});
499+
}
457500
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.jooby
2+
3+
import kotlinx.coroutines.Dispatchers
4+
import kotlinx.coroutines.delay
5+
import kotlinx.coroutines.withContext
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.Test
8+
9+
class CoroutineUnitTest {
10+
@Test
11+
fun shouldSupportUnitTestWhileUsingCoroutineRouter() {
12+
val app = Kooby {
13+
coroutine {
14+
get("/coroutine") {
15+
withContext(coroutineScope.coroutineContext) {
16+
ctx.requestPath
17+
}
18+
}
19+
20+
get("/delay") {
21+
delay(100)
22+
ctx.requestPath
23+
}
24+
25+
get("/heavy-task") {
26+
withContext(Dispatchers.IO) {
27+
ctx.responseType = MediaType.text
28+
ctx.responseCode = StatusCode.CREATED
29+
ctx.send(ctx.requestPath)
30+
}
31+
ctx
32+
}
33+
}
34+
}
35+
36+
val router = MockRouter(app)
37+
38+
assertEquals("/coroutine", router["/coroutine"].value())
39+
assertEquals("/delay", router["/delay"].value())
40+
41+
router.get("/heavy-task") { rsp ->
42+
assertEquals("/heavy-task", rsp.value())
43+
assertEquals(MediaType.text, rsp.contentType)
44+
assertEquals(StatusCode.CREATED, rsp.statusCode)
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)