2222import org .junit .jupiter .api .Nested ;
2323import org .junit .jupiter .api .Test ;
2424
25+ import org .springframework .graphql .data .method .annotation .Argument ;
2526import org .springframework .graphql .data .method .annotation .QueryMapping ;
2627import org .springframework .graphql .execution .SchemaMappingInspector .ClassResolver ;
2728import org .springframework .stereotype .Controller ;
@@ -35,16 +36,20 @@ public class SchemaMappingInspectorUnionTests extends SchemaMappingInspectorTest
3536
3637 private static final String schema = """
3738 type Query {
38- search: [SearchResult!]!
39+ search: [SearchResult]
40+ article(id: ID): Article
3941 }
40- union SearchResult = Photo | Video
42+ union SearchResult = Article | Photo | Video
4143 type Photo {
4244 height: Int
4345 width: Int
4446 }
4547 type Video {
4648 title: String
4749 }
50+ type Article {
51+ content: String
52+ }
4853 """ ;
4954
5055
@@ -55,8 +60,10 @@ class UnmappedFields {
5560 void reportUnmappedFieldsByCheckingReturnTypePackage () {
5661 SchemaReport report = inspectSchema (schema , SearchController .class );
5762 assertThatReport (report )
58- .hasSkippedTypeCount (0 )
59- .hasUnmappedFieldCount (3 )
63+ .hasSkippedTypeCount (1 )
64+ .containsSkippedTypes ("Article" )
65+ .hasUnmappedFieldCount (4 )
66+ .containsUnmappedFields ("Query" , "article" )
6067 .containsUnmappedFields ("Photo" , "height" , "width" )
6168 .containsUnmappedFields ("Video" , "title" );
6269 }
@@ -65,17 +72,20 @@ void reportUnmappedFieldsByCheckingReturnTypePackage() {
6572 void reportUnmappedFieldsByCheckingControllerTypePackage () {
6673 SchemaReport report = inspectSchema (schema , ObjectSearchController .class );
6774 assertThatReport (report )
68- .hasSkippedTypeCount (0 )
69- .hasUnmappedFieldCount (3 )
75+ .hasSkippedTypeCount (1 )
76+ .containsSkippedTypes ("Article" )
77+ .hasUnmappedFieldCount (4 )
78+ .containsUnmappedFields ("Query" , "article" )
7079 .containsUnmappedFields ("Photo" , "height" , "width" )
7180 .containsUnmappedFields ("Video" , "title" );
7281 }
7382
7483
75- sealed interface ResultItem permits Photo , Video { }
7684 record Photo () implements ResultItem { }
7785 record Video () implements ResultItem { }
7886
87+ sealed interface ResultItem permits Photo , Video { }
88+
7989 @ Controller
8090 static class SearchController {
8191
@@ -108,8 +118,10 @@ void classNameFunction() {
108118 SearchController .class );
109119
110120 assertThatReport (report )
111- .hasSkippedTypeCount (0 )
112- .hasUnmappedFieldCount (3 )
121+ .hasSkippedTypeCount (1 )
122+ .containsSkippedTypes ("Article" )
123+ .hasUnmappedFieldCount (4 )
124+ .containsUnmappedFields ("Query" , "article" )
113125 .containsUnmappedFields ("Photo" , "height" , "width" )
114126 .containsUnmappedFields ("Video" , "title" );
115127 }
@@ -124,8 +136,11 @@ void classNameTypeResolver() {
124136 SearchController .class );
125137
126138 assertThatReport (report )
127- .hasUnmappedFieldCount (2 ).containsUnmappedFields ("Photo" , "height" , "width" )
128- .hasSkippedTypeCount (1 ).containsSkippedTypes ("Video" );
139+ .hasSkippedTypeCount (2 )
140+ .containsSkippedTypes ("Article" , "Video" )
141+ .hasUnmappedFieldCount (3 )
142+ .containsUnmappedFields ("Query" , "article" )
143+ .containsUnmappedFields ("Photo" , "height" , "width" );
129144 }
130145
131146 sealed interface ResultItem permits PhotoImpl , VideoImpl { }
@@ -149,7 +164,7 @@ class SkippedTypes {
149164 @ Test
150165 void reportSkippedImplementations () {
151166 SchemaReport report = inspectSchema (schema , SearchController .class );
152- assertThatReport (report ).hasSkippedTypeCount (2 ).containsSkippedTypes ("Photo" , "Video" );
167+ assertThatReport (report ).hasSkippedTypeCount (3 ).containsSkippedTypes ("Article" , "Photo" , "Video" );
153168 }
154169
155170 interface ResultItem { }
@@ -164,4 +179,38 @@ List<ResultItem> search() {
164179 }
165180 }
166181
182+
183+ @ Nested
184+ class CandidateSkippedTypes {
185+
186+ // A union member type is only a candidate to be skipped until the inspection is done.
187+ // Use of the concrete type elsewhere may provide more information.
188+
189+ @ Test
190+ void candidateNotSkippedIfConcreteUseElsewhere () {
191+ SchemaReport report = inspectSchema (schema , SearchController .class );
192+ assertThatReport (report ).hasSkippedTypeCount (2 ).containsSkippedTypes ("Photo" , "Video" );
193+ }
194+
195+ @ Controller
196+ static class SearchController {
197+
198+ @ QueryMapping
199+ List <Object > search () {
200+ throw new UnsupportedOperationException ();
201+ }
202+
203+ @ QueryMapping
204+ Article article (@ Argument Long id ) {
205+ throw new UnsupportedOperationException ();
206+ }
207+ }
208+ }
209+
210+
211+ /**
212+ * Declared outside {@link CandidateSkippedTypes}, so the union lookup won't find it.
213+ */
214+ private record Article (String content ) { }
215+
167216}
0 commit comments