Skip to content

Commit b68f2d8

Browse files
committed
build: add more unit tests
1 parent 42e645f commit b68f2d8

18 files changed

Lines changed: 1815 additions & 1 deletion

File tree

jooby/src/main/java/io/jooby/ServiceRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ static <T> MultiBinder<T> set() {
113113
@SuppressWarnings("unchecked")
114114
@Override
115115
public Set<T> get() {
116-
return (Set<T>) Set.of(services.stream().map(Provider::get).toArray());
116+
return (Set<T>) Set.of(services.stream().map(Provider::get).distinct().toArray());
117117
}
118118
};
119119
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
import static org.mockito.Mockito.*;
10+
11+
import java.util.Map;
12+
import java.util.concurrent.TimeUnit;
13+
14+
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.Test;
16+
17+
import io.jooby.value.Value;
18+
import io.jooby.value.ValueFactory;
19+
20+
public class ServerSentEmitterTest {
21+
22+
private ServerSentEmitter emitter;
23+
private Context ctx;
24+
25+
@BeforeEach
26+
void setUp() {
27+
emitter = mock(ServerSentEmitter.class);
28+
ctx = mock(Context.class);
29+
30+
// Wire up the mock to execute the default interface methods
31+
when(emitter.getContext()).thenReturn(ctx);
32+
when(emitter.getAttributes()).thenCallRealMethod();
33+
when(emitter.attribute(anyString())).thenCallRealMethod();
34+
when(emitter.attribute(anyString(), any())).thenCallRealMethod();
35+
when(emitter.send(anyString())).thenCallRealMethod();
36+
when(emitter.send(any(byte[].class))).thenCallRealMethod();
37+
when(emitter.send(any(Object.class))).thenCallRealMethod();
38+
when(emitter.send(anyString(), any())).thenCallRealMethod();
39+
when(emitter.keepAlive(anyLong(), any(TimeUnit.class))).thenCallRealMethod();
40+
when(emitter.getLastEventId()).thenCallRealMethod();
41+
when(emitter.lastEventId(any())).thenCallRealMethod();
42+
}
43+
44+
@Test
45+
void testKeepAliveTaskSuccess() {
46+
when(emitter.isOpen()).thenReturn(true);
47+
when(emitter.getId()).thenReturn("sse-123");
48+
49+
ServerSentEmitter.KeepAlive keepAlive = new ServerSentEmitter.KeepAlive(emitter, 5000L);
50+
keepAlive.run();
51+
52+
verify(emitter).send(":sse-123\n");
53+
verify(emitter).keepAlive(5000L);
54+
}
55+
56+
@Test
57+
void testKeepAliveTaskError() {
58+
when(emitter.isOpen()).thenReturn(true);
59+
when(emitter.getId()).thenReturn("sse-123");
60+
doThrow(new RuntimeException("Link dead")).when(emitter).send(anyString());
61+
62+
ServerSentEmitter.KeepAlive keepAlive = new ServerSentEmitter.KeepAlive(emitter, 5000L);
63+
keepAlive.run();
64+
65+
verify(emitter).close();
66+
}
67+
68+
@Test
69+
void testKeepAliveTaskWhenClosed() {
70+
when(emitter.isOpen()).thenReturn(false);
71+
72+
ServerSentEmitter.KeepAlive keepAlive = new ServerSentEmitter.KeepAlive(emitter, 5000L);
73+
keepAlive.run();
74+
75+
verify(emitter, never()).send(anyString());
76+
}
77+
78+
@Test
79+
void testAttributeMethods() {
80+
Map<String, Object> attrs = Map.of("k", "v");
81+
when(ctx.getAttributes()).thenReturn(attrs);
82+
when(ctx.getAttribute("k")).thenReturn("v");
83+
84+
// 1. Test getAttributes()
85+
assertEquals(attrs, emitter.getAttributes());
86+
87+
// 2. Test attribute(key)
88+
assertEquals("v", emitter.attribute("k"));
89+
90+
// 3. Test attribute(key, value)
91+
// We do NOT stub this; we let thenCallRealMethod() from setUp run it
92+
ServerSentEmitter result = emitter.attribute("name", "jooby");
93+
94+
assertEquals(emitter, result);
95+
verify(ctx).setAttribute("name", "jooby");
96+
}
97+
98+
@Test
99+
void testSendDefaultMethods() {
100+
// 1. String send
101+
emitter.send("hello");
102+
verify(emitter)
103+
.send(
104+
(ServerSentMessage)
105+
argThat(
106+
msg ->
107+
msg instanceof ServerSentMessage sseMsg
108+
&& "hello".equals(sseMsg.getData())));
109+
110+
// 2. Byte array send
111+
byte[] bytes = new byte[] {1, 2};
112+
emitter.send(bytes);
113+
verify(emitter)
114+
.send(
115+
(ServerSentMessage)
116+
argThat(
117+
msg -> msg instanceof ServerSentMessage sseMsg && sseMsg.getData() == bytes));
118+
119+
// 3. Object send (non-SSE message)
120+
emitter.send((Object) 123);
121+
verify(emitter)
122+
.send(
123+
(ServerSentMessage)
124+
argThat(
125+
msg ->
126+
msg instanceof ServerSentMessage sseMsg
127+
&& Integer.valueOf(123).equals(sseMsg.getData())));
128+
129+
// 4. Object send (is-SSE message)
130+
ServerSentMessage sseMsg = new ServerSentMessage("data");
131+
emitter.send((Object) sseMsg);
132+
verify(emitter).send(sseMsg);
133+
134+
// 5. Event + Data send
135+
emitter.send("update", "payload");
136+
verify(emitter)
137+
.send(
138+
(ServerSentMessage)
139+
argThat(
140+
msg ->
141+
msg instanceof ServerSentMessage sseMsg1
142+
&& "payload".equals(sseMsg1.getData())
143+
&& "update".equals(sseMsg1.getEvent())));
144+
}
145+
146+
@Test
147+
void testKeepAliveWithUnit() {
148+
emitter.keepAlive(1, TimeUnit.SECONDS);
149+
verify(emitter).keepAlive(1000L);
150+
}
151+
152+
@Test
153+
void testLastEventId() {
154+
// Header present
155+
when(ctx.header("Last-Event-ID"))
156+
.thenReturn(Value.value(new ValueFactory(), "Last-Event-ID", "100"));
157+
assertEquals("100", emitter.getLastEventId());
158+
assertEquals(100, (Integer) emitter.lastEventId(Integer.class));
159+
160+
// Header missing
161+
when(ctx.header("Last-Event-ID"))
162+
.thenReturn(Value.missing(new ValueFactory(), "Last-Event-ID"));
163+
assertNull(emitter.getLastEventId());
164+
}
165+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
import java.util.*;
11+
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.Test;
14+
15+
import io.jooby.exception.RegistryException;
16+
import jakarta.inject.Provider;
17+
18+
public class ServiceRegistryTest {
19+
20+
/** Simple concrete implementation of ServiceRegistry for testing default methods. */
21+
private static class TestRegistry implements ServiceRegistry {
22+
private final Map<ServiceKey<?>, Provider<?>> storage = new HashMap<>();
23+
24+
@Override
25+
public Set<ServiceKey<?>> keySet() {
26+
return storage.keySet();
27+
}
28+
29+
@Override
30+
public Set<Map.Entry<ServiceKey<?>, Provider<?>>> entrySet() {
31+
return storage.entrySet();
32+
}
33+
34+
@SuppressWarnings("unchecked")
35+
@Override
36+
public <T> T getOrNull(ServiceKey<T> key) {
37+
Provider<T> provider = (Provider<T>) storage.get(key);
38+
return provider != null ? provider.get() : null;
39+
}
40+
41+
@Override
42+
public <T> T put(ServiceKey<T> key, Provider<T> service) {
43+
storage.put(key, service);
44+
return null; // Simplified
45+
}
46+
47+
@Override
48+
public <T> T put(ServiceKey<T> key, T service) {
49+
return put(key, (Provider<T>) () -> service);
50+
}
51+
52+
@SuppressWarnings("unchecked")
53+
@Override
54+
public <T> T putIfAbsent(ServiceKey<T> key, Provider<T> service) {
55+
return (T) storage.putIfAbsent(key, service);
56+
}
57+
58+
@Override
59+
public <T> T putIfAbsent(ServiceKey<T> key, T service) {
60+
return putIfAbsent(key, (Provider<T>) () -> service);
61+
}
62+
}
63+
64+
private TestRegistry registry;
65+
66+
@BeforeEach
67+
void setUp() {
68+
registry = new TestRegistry();
69+
}
70+
71+
@Test
72+
void testMapBinder() {
73+
ServiceRegistry.MapBinder<String, Integer> binder = registry.mapOf(String.class, Integer.class);
74+
binder.put("one", 1);
75+
binder.put("two", () -> 2);
76+
77+
Map<String, Integer> map = binder.get();
78+
assertEquals(1, map.get("one"));
79+
assertEquals(2, map.get("two"));
80+
assertThrows(UnsupportedOperationException.class, () -> map.put("three", 3)); // Unmodifiable
81+
}
82+
83+
@Test
84+
void testMultiBinderList() {
85+
ServiceRegistry.MultiBinder<String> binder = registry.listOf(String.class);
86+
binder.add("a").add(() -> "b");
87+
88+
List<String> list = (List<String>) binder.get();
89+
assertEquals(List.of("a", "b"), list);
90+
91+
// Test Reified variant
92+
ServiceRegistry.MultiBinder<String> reifiedBinder = registry.listOf(Reified.get(String.class));
93+
reifiedBinder.add("c");
94+
assertTrue(reifiedBinder.get().contains("c"));
95+
}
96+
97+
@Test
98+
void testMultiBinderSet() {
99+
ServiceRegistry.MultiBinder<String> binder = registry.setOf(String.class);
100+
binder.add("a").add("a"); // Duplicate
101+
102+
Set<String> set = (Set<String>) binder.get();
103+
assertEquals(1, set.size());
104+
105+
// Test Reified variant
106+
ServiceRegistry.MultiBinder<String> reifiedBinder = registry.setOf(Reified.get(String.class));
107+
assertNotNull(reifiedBinder);
108+
}
109+
110+
@Test
111+
void testGetVariants() {
112+
registry.put(String.class, "hello");
113+
114+
assertEquals("hello", registry.get(String.class));
115+
assertEquals("hello", registry.get(Reified.get(String.class)));
116+
assertEquals("hello", registry.require(String.class));
117+
assertEquals("hello", registry.require(Reified.get(String.class)));
118+
119+
registry.put(ServiceKey.key(String.class, "named"), "world");
120+
assertEquals("world", registry.require(String.class, "named"));
121+
assertEquals("world", registry.require(Reified.get(String.class), "named"));
122+
}
123+
124+
@Test
125+
void testGetNotFound() {
126+
assertThrows(RegistryException.class, () -> registry.get(String.class));
127+
assertNull(registry.getOrNull(String.class));
128+
assertNull(registry.getOrNull(Reified.get(String.class)));
129+
}
130+
131+
@Test
132+
void testPutIfAbsentVariants() {
133+
registry.putIfAbsent(String.class, "first");
134+
registry.putIfAbsent(String.class, "second");
135+
assertEquals("first", registry.get(String.class));
136+
137+
registry.putIfAbsent(Integer.class, (Provider<Integer>) () -> 10);
138+
assertEquals(10, registry.get(Integer.class));
139+
140+
registry.putIfAbsent(Reified.get(Long.class), 100L);
141+
assertEquals(100L, registry.get(Long.class));
142+
143+
registry.putIfAbsent(Double.class, (Provider<Double>) () -> 1.1);
144+
assertEquals(1.1, registry.get(Double.class));
145+
}
146+
147+
@Test
148+
void testMultiBinderTypeMismatch() {
149+
// Register a raw String where a MapBinder is expected
150+
registry.put(
151+
ServiceKey.key(Reified.map(String.class, String.class)),
152+
new Provider<Map<Object, Object>>() {
153+
@Override
154+
public Map<Object, Object> get() {
155+
return Map.of("key", "value");
156+
}
157+
});
158+
159+
assertThrows(RegistryException.class, () -> registry.mapOf(String.class, String.class));
160+
}
161+
162+
@Test
163+
void testMapOfWithReified() {
164+
ServiceRegistry.MapBinder<String, List<String>> binder =
165+
registry.mapOf(String.class, new Reified<List<String>>() {});
166+
assertNotNull(binder);
167+
}
168+
}

modules/jooby-kotlin/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747
<artifactId>mockito-core</artifactId>
4848
<scope>test</scope>
4949
</dependency>
50+
<dependency>
51+
<groupId>io.mockk</groupId>
52+
<artifactId>mockk-jvm</artifactId>
53+
<version>1.14.9</version>
54+
<scope>test</scope>
55+
</dependency>
5056
</dependencies>
5157

5258
<build>

0 commit comments

Comments
 (0)