github.com/99designs/gqlgen@v0.17.45/codegen/testserver/singlefile/interfaces_test.go (about) 1 package singlefile 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "testing" 8 9 "github.com/stretchr/testify/require" 10 11 "github.com/99designs/gqlgen/client" 12 "github.com/99designs/gqlgen/graphql" 13 "github.com/99designs/gqlgen/graphql/handler" 14 ) 15 16 func TestInterfaces(t *testing.T) { 17 t.Run("slices of interfaces are not pointers", func(t *testing.T) { 18 field, ok := reflect.TypeOf((*QueryResolver)(nil)).Elem().MethodByName("Shapes") 19 require.True(t, ok) 20 require.Equal(t, "[]singlefile.Shape", field.Type.Out(0).String()) 21 }) 22 23 t.Run("models returning interfaces", func(t *testing.T) { 24 resolvers := &Stub{} 25 resolvers.QueryResolver.Node = func(ctx context.Context) (node Node, err error) { 26 return &ConcreteNodeA{ 27 ID: "1234", 28 Name: "asdf", 29 child: &ConcreteNodeA{ 30 ID: "5678", 31 Name: "hjkl", 32 child: nil, 33 }, 34 }, nil 35 } 36 37 srv := handler.NewDefaultServer( 38 NewExecutableSchema(Config{ 39 Resolvers: resolvers, 40 }), 41 ) 42 43 c := client.New(srv) 44 45 var resp struct { 46 Node struct { 47 ID string 48 Child struct { 49 ID string 50 } 51 } 52 } 53 c.MustPost(`{ node { id, child { id } } }`, &resp) 54 require.Equal(t, "1234", resp.Node.ID) 55 require.Equal(t, "5678", resp.Node.Child.ID) 56 }) 57 58 t.Run("interfaces can be nil", func(t *testing.T) { 59 resolvers := &Stub{} 60 resolvers.QueryResolver.NoShape = func(ctx context.Context) (shapes Shape, e error) { 61 return nil, nil 62 } 63 64 srv := handler.NewDefaultServer( 65 NewExecutableSchema(Config{ 66 Resolvers: resolvers, 67 Directives: DirectiveRoot{ 68 MakeNil: func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { 69 return nil, nil 70 }, 71 }, 72 }), 73 ) 74 75 c := client.New(srv) 76 77 var resp interface{} 78 c.MustPost(`{ noShape { area } }`, &resp) 79 }) 80 81 t.Run("interfaces can be typed nil", func(t *testing.T) { 82 resolvers := &Stub{} 83 resolvers.QueryResolver.NoShapeTypedNil = func(ctx context.Context) (shapes Shape, e error) { 84 panic("should not be called") 85 } 86 87 srv := handler.NewDefaultServer( 88 NewExecutableSchema(Config{ 89 Resolvers: resolvers, 90 Directives: DirectiveRoot{ 91 MakeTypedNil: func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { 92 var circle *Circle 93 return circle, nil 94 }, 95 }, 96 }), 97 ) 98 99 c := client.New(srv) 100 101 var resp interface{} 102 c.MustPost(`{ noShapeTypedNil { area } }`, &resp) 103 }) 104 105 t.Run("interfaces can be nil (test with code-generated resolver)", func(t *testing.T) { 106 resolvers := &Stub{} 107 resolvers.QueryResolver.Animal = func(ctx context.Context) (animal Animal, e error) { 108 panic("should not be called") 109 } 110 111 srv := handler.NewDefaultServer( 112 NewExecutableSchema(Config{ 113 Resolvers: resolvers, 114 Directives: DirectiveRoot{ 115 MakeTypedNil: func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { 116 var dog *Dog // return a typed nil, not just nil 117 return dog, nil 118 }, 119 }, 120 }), 121 ) 122 123 c := client.New(srv) 124 125 var resp interface{} 126 c.MustPost(`{ animal { species } }`, &resp) 127 }) 128 129 t.Run("can bind to interfaces even when the graphql is not", func(t *testing.T) { 130 resolvers := &Stub{} 131 resolvers.BackedByInterfaceResolver.ID = func(ctx context.Context, obj BackedByInterface) (s string, err error) { 132 return "ID:" + obj.ThisShouldBind(), nil 133 } 134 resolvers.QueryResolver.NotAnInterface = func(ctx context.Context) (byInterface BackedByInterface, err error) { 135 return &BackedByInterfaceImpl{ 136 Value: "A", 137 Error: nil, 138 }, nil 139 } 140 141 c := client.New(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers}))) 142 143 var resp struct { 144 NotAnInterface struct { 145 ID string 146 ThisShouldBind string 147 ThisShouldBindWithError string 148 } 149 } 150 c.MustPost(`{ notAnInterface { id, thisShouldBind, thisShouldBindWithError } }`, &resp) 151 require.Equal(t, "ID:A", resp.NotAnInterface.ID) 152 require.Equal(t, "A", resp.NotAnInterface.ThisShouldBind) 153 require.Equal(t, "A", resp.NotAnInterface.ThisShouldBindWithError) 154 }) 155 156 t.Run("can return errors from interface funcs", func(t *testing.T) { 157 resolvers := &Stub{} 158 resolvers.BackedByInterfaceResolver.ID = func(ctx context.Context, obj BackedByInterface) (s string, err error) { 159 return "ID:" + obj.ThisShouldBind(), nil 160 } 161 resolvers.QueryResolver.NotAnInterface = func(ctx context.Context) (byInterface BackedByInterface, err error) { 162 return &BackedByInterfaceImpl{ 163 Value: "A", 164 Error: fmt.Errorf("boom"), 165 }, nil 166 } 167 168 c := client.New(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers}))) 169 170 var resp struct { 171 NotAnInterface struct { 172 ID string 173 ThisShouldBind string 174 ThisShouldBindWithError string 175 } 176 } 177 err := c.Post(`{ notAnInterface { id, thisShouldBind, thisShouldBindWithError } }`, &resp) 178 require.EqualError(t, err, `[{"message":"boom","path":["notAnInterface","thisShouldBindWithError"]}]`) 179 }) 180 181 t.Run("interfaces can implement other interfaces", func(t *testing.T) { 182 resolvers := &Stub{} 183 resolvers.QueryResolver.Node = func(ctx context.Context) (node Node, err error) { 184 return ConcreteNodeInterfaceImplementor{}, nil 185 } 186 187 c := client.New(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers}))) 188 189 var resp struct { 190 Node struct { 191 ID string 192 Child struct { 193 ID string 194 } 195 } 196 } 197 c.MustPost(`{ node { id, child { id } } }`, &resp) 198 require.Equal(t, "CNII", resp.Node.ID) 199 require.Equal(t, "Child", resp.Node.Child.ID) 200 }) 201 202 t.Run("interface implementors should return merged base fields", func(t *testing.T) { 203 resolvers := &Stub{} 204 resolvers.QueryResolver.Shapes = func(ctx context.Context) (shapes []Shape, err error) { 205 return []Shape{ 206 &Rectangle{ 207 Coordinates: Coordinates{ 208 X: -1, 209 Y: -1, 210 }, 211 }, 212 &Circle{ 213 Coordinates: Coordinates{ 214 X: 1, 215 Y: 1, 216 }, 217 }, 218 }, nil 219 } 220 221 c := client.New(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers}))) 222 var resp struct { 223 Shapes []struct { 224 Coordinates struct { 225 X float64 226 Y float64 227 } 228 } 229 } 230 231 c.MustPost(` 232 { 233 shapes { 234 coordinates { 235 x 236 } 237 ... on Rectangle { 238 coordinates { 239 x 240 } 241 } 242 ... on Circle { 243 coordinates { 244 y 245 } 246 } 247 } 248 } 249 `, &resp) 250 251 require.Equal(t, 2, len(resp.Shapes)) 252 require.Equal(t, float64(-1), resp.Shapes[0].Coordinates.X) 253 require.Equal(t, float64(0), resp.Shapes[0].Coordinates.Y) 254 require.Equal(t, float64(1), resp.Shapes[1].Coordinates.X) 255 require.Equal(t, float64(1), resp.Shapes[1].Coordinates.Y) 256 }) 257 258 t.Run("fragment on interface must return merged fields", func(t *testing.T) { 259 resolvers := &Stub{} 260 resolvers.QueryResolver.Dog = func(ctx context.Context) (dog *Dog, err error) { 261 return &Dog{ 262 Size: &Size{ 263 Height: 100, 264 Weight: 35, 265 }, 266 }, nil 267 } 268 269 c := client.New(handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: resolvers}))) 270 var resp struct { 271 Dog struct { 272 Size struct { 273 Height int 274 Weight int 275 } 276 } 277 } 278 279 c.MustPost(` 280 { 281 dog { 282 size { 283 height 284 } 285 ...AnimalWeight 286 } 287 } 288 fragment AnimalWeight on Animal { 289 size { 290 weight 291 } 292 } 293 `, &resp) 294 295 require.Equal(t, 100, resp.Dog.Size.Height) 296 require.Equal(t, 35, resp.Dog.Size.Weight) 297 }) 298 }