Skip to content

Commit 36eaa63

Browse files
committed
#202 - Can not apply Spy to a bean in the test scope
1 parent 3f3d1fb commit 36eaa63

File tree

9 files changed

+193
-9
lines changed

9 files changed

+193
-9
lines changed

inject-test/src/test/java/org/example/custom2/OcsOne.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33
@OtherScope
44
public class OcsOne implements OciPlant, OciRock {
55

6+
public String one() {
7+
return "realOne";
8+
}
69
}

inject-test/src/test/java/org/example/custom2/OcsTwo.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.example.custom2;
22

3+
import jakarta.inject.Named;
4+
5+
@Named("two")
36
@OciMarker
47
@OtherScope
58
public class OcsTwo implements OciRock {
@@ -9,4 +12,8 @@ public class OcsTwo implements OciRock {
912
OcsTwo(OcsOne one) {
1013
this.one = one;
1114
}
15+
16+
public String twoPlusOne() {
17+
return "two+" + one.one();
18+
}
1219
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package org.example.custom2;
2+
3+
import io.avaje.inject.BeanScope;
4+
import io.avaje.inject.spi.Builder;
5+
import io.avaje.inject.spi.Module;
6+
import org.junit.jupiter.api.Test;
7+
import org.mockito.Mockito;
8+
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
11+
class ParentScopeSpyTest {
12+
13+
@Test
14+
void parent() {
15+
try (var parent = BeanScope.builder()
16+
.modules(new MyTestModule())
17+
.build()) {
18+
19+
var parentOne = parent.get(OcsOne.class);
20+
assertThat(parentOne.one()).isEqualTo("realOne");
21+
22+
try (var child = BeanScope.builder()
23+
.parent(parent, false)
24+
.modules(new OtherModule())
25+
.build()) {
26+
27+
OcsThree three = child.get(OcsThree.class);
28+
assertThat(three).isNotNull();
29+
var childOne = child.get(OcsOne.class);
30+
assertThat(childOne).isSameAs(parentOne);
31+
32+
OcsTwo two = child.get(OcsTwo.class);
33+
assertThat(two.twoPlusOne()).isEqualTo("two+realOne");
34+
}
35+
}
36+
}
37+
38+
@Test
39+
void parent_withSpy() {
40+
try (var parent = BeanScope.builder()
41+
.modules(new MyTestModule())
42+
.build()) {
43+
44+
var parentOne = parent.get(OcsOne.class);
45+
assertThat(parentOne.one()).isEqualTo("realOne");
46+
47+
try (var child = BeanScope.builder()
48+
.parent(parent, false)
49+
.modules(new OtherModule())
50+
.forTesting()
51+
.spy(OcsOne.class) // apply Spy to parent supplied bean
52+
.build()) {
53+
54+
assertThat(child.get(OcsThree.class)).isNotNull();
55+
var childOne = child.get(OcsOne.class);
56+
assertThat(childOne).isNotSameAs(parentOne);
57+
58+
OcsTwo two = child.get(OcsTwo.class);
59+
assertThat(two.twoPlusOne()).isEqualTo("two+realOne");
60+
61+
// it's a spy
62+
Mockito.verify(childOne).one();
63+
}
64+
}
65+
}
66+
67+
static class MyTestModule implements Module.Custom {
68+
69+
private final Class<?>[] provides = new Class<?>[]{};
70+
private final Class<?>[] requires = new Class<?>[]{};
71+
private final Class<?>[] requiresPackages = new Class<?>[]{};
72+
private Builder builder;
73+
74+
@Override
75+
public Class<?>[] provides() {
76+
return provides;
77+
}
78+
79+
@Override
80+
public Class<?>[] requires() {
81+
return requires;
82+
}
83+
84+
@Override
85+
public Class<?>[] requiresPackages() {
86+
return requiresPackages;
87+
}
88+
89+
@Override
90+
public Class<?>[] classes() {
91+
return new Class<?>[]{
92+
org.example.custom2.OcsOne.class,
93+
};
94+
}
95+
96+
@Override
97+
public void build(Builder builder) {
98+
this.builder = builder;
99+
build_custom2_OcsOne();
100+
}
101+
102+
protected void build_custom2_OcsOne() {
103+
OcsOne$DI.build(builder);
104+
}
105+
106+
}
107+
}

inject/src/main/java/io/avaje/inject/spi/DBeanMap.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ void register(int flag, Provider<?> provider) {
7373
}
7474
}
7575

76+
/**
77+
* Get with a strict match on name for the single entry case.
78+
*/
79+
Object getStrict(Type type, String name) {
80+
DContextEntry entry = beans.get(type.getTypeName());
81+
if (entry == null) {
82+
return null;
83+
}
84+
return entry.getStrict(KeyUtil.lower(name));
85+
}
86+
7687
@SuppressWarnings("unchecked")
7788
<T> T get(Type type, String name) {
7889
DContextEntry entry = beans.get(type.getTypeName());

inject/src/main/java/io/avaje/inject/spi/DBeanScope.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ private <T> T getByType(Type type, @Nullable String name) {
8585
return parent.get(type, name);
8686
}
8787

88+
/**
89+
* Get with a strict match on name for the single entry case.
90+
*/
91+
@Nullable
92+
Object getStrict(String name, Type[] types) {
93+
for (Type type : types) {
94+
if (!isAnnotationType(type)) {
95+
Object match = beans.getStrict(type, name);
96+
if (match != null) {
97+
return match;
98+
}
99+
}
100+
}
101+
return null;
102+
}
103+
104+
private boolean isAnnotationType(Type type) {
105+
return type instanceof Class && ((Class<?>) type).isAnnotation();
106+
}
107+
88108
@Override
89109
public <T> Optional<T> getOptional(Class<T> type) {
90110
return getMaybe(type, null);

inject/src/main/java/io/avaje/inject/spi/DBuilder.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,13 @@ class DBuilder implements Builder {
2828
*/
2929
protected final DBeanMap beanMap = new DBeanMap();
3030

31-
private final BeanScope parent;
32-
private final boolean parentOverride;
31+
protected final BeanScope parent;
32+
protected final boolean parentOverride;
33+
34+
/**
35+
* Bean provided by the parent scope that we are not overriding.
36+
*/
37+
protected Object parentMatch;
3338

3439
/**
3540
* Debug of the current bean being wired - used in injection errors.
@@ -53,17 +58,16 @@ public boolean isAddBeanFor(Type... types) {
5358

5459
@Override
5560
public boolean isAddBeanFor(String name, Type... types) {
61+
parentMatch = null;
5662
next(name, types);
5763
if (parentOverride || parent == null) {
5864
return true;
5965
}
60-
for (Type type : types) {
61-
try {
62-
parent.get(type, name);
63-
return false;
64-
} catch (NoSuchElementException e) {
65-
// ignore
66-
}
66+
if (parent instanceof DBeanScope) {
67+
// effectively looking for a match in the test scope
68+
DBeanScope dParent = (DBeanScope) parent;
69+
parentMatch = dParent.getStrict(name, types);
70+
return parentMatch == null;
6771
}
6872
return true;
6973
}

inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.avaje.inject.spi;
22

3+
import io.avaje.inject.BeanEntry;
34
import io.avaje.inject.BeanScope;
45

56
import java.lang.reflect.Type;
@@ -34,6 +35,7 @@ class DBuilderExtn extends DBuilder {
3435
@Override
3536
public boolean isAddBeanFor(String qualifierName, Type... types) {
3637
if (!super.isAddBeanFor(qualifierName, types)) {
38+
enrichParentMatch();
3739
return false;
3840
}
3941
if (hasSuppliedBeans) {
@@ -42,6 +44,19 @@ public boolean isAddBeanFor(String qualifierName, Type... types) {
4244
return true;
4345
}
4446

47+
/**
48+
* If we have a parentMatch (e.g. test scope bean) but we want to enrich it (Mockito Spy),
49+
* then enrich the parentMatch bean and register that into this scope.
50+
*/
51+
private void enrichParentMatch() {
52+
if (parentMatch != null && !enrichMap.isEmpty()) {
53+
Object enrichedBean = enrich(parentMatch, beanMap.next());
54+
if (enrichedBean != parentMatch) {
55+
beanMap.register(BeanEntry.SUPPLIED, enrichedBean);
56+
}
57+
}
58+
}
59+
4560
/**
4661
* Potentially enrich the bean prior to registering with context.
4762
*/

inject/src/main/java/io/avaje/inject/spi/DContextEntry.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ Provider<?> provider(String name) {
3434
return new EntryMatcher(name).provider(entries);
3535
}
3636

37+
/**
38+
* Get with strict name match for the single entry case.
39+
*/
40+
Object getStrict(String name) {
41+
if (entries.size() == 1) {
42+
return entries.get(0).beanIfNameMatch(name);
43+
}
44+
return new EntryMatcher(name).match(entries);
45+
}
46+
3747
Object get(String name) {
3848
if (entries.size() == 1) {
3949
return entries.get(0).bean();

inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ final boolean isNameEqual(String qualifierName) {
6666
return Objects.equals(qualifierName, name);
6767
}
6868

69+
/**
70+
* Return the bean if name matches and otherwise null.
71+
*/
72+
Object beanIfNameMatch(String name) {
73+
return isNameMatch(name) ? bean() : null;
74+
}
75+
6976
Object bean() {
7077
return source;
7178
}

0 commit comments

Comments
 (0)