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  }