...

Source file src/github.com/99designs/gqlgen/codegen/testserver/followschema/interfaces_test.go

Documentation: github.com/99designs/gqlgen/codegen/testserver/followschema

     1  package followschema
     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, "[]followschema.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  }
   299  

View as plain text