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