Repository: strawberry-graphql/strawberry

Issue:
Add test for/document interface type resolution
I switched over from graphene for a small test and have a hard time figuring out how to solve the

```
Abstract type NodeIface must resolve to an Object type at runtime for field Root.nodes with value <Node instance>, received 'None'. Either the NodeIface type should provide a \"resolve_type\" function or each possible type should provide an \"is_type_of\" function.
```

error message.

What I have so far is:

```python
@strawberry.interface
class NodeIface:
    uuid: strawberry.ID
    ctime: str
    mtime: str
    label: str

@strawberry.type
class Node(NodeIface):
    pass

@strawberry.type
class Calculation(NodeIface):
    attributes: CalculationAttributes
```

if I do something like:

```python
@strawberry.type
class Root:
    @strawberry.field
    def nodes(self, info) -> typing.List[typing.Union[Calculation, Node]]:
        # do a dispatch to the correct type here based on DB data
        def dispatch(dbobj):
            if dbobj.is_calculation:
                return Calculation(**dbobj)
            return Node(**dbobj)
        return [dispatch(dbobj) for dbobj in db.all()]
```

I can write a query like:

```graphql
{
  nodes {
    __typename
    ... on Calculation {
      attributes {
        processState
      }
    }
  }
}
```

but the following fails:

```graphql
{
  nodes {
    uuid
    __typename
    ... on Calculation {
      attributes {
        processState
      }
    }
  }
}
```

with

```
{
  "error": {
    "data": null,
    "errors": [
      {
        "message": "Cannot query field 'uuid' on type 'nodes'. Did you mean to use an inline fragment on 'NodeIface', 'Calculation' or 'Node'?",
        "locations": [
          {
            "line": 3,
            "column": 5
          }
        ],
        "path": null
      }
    ]
  }
}
```

On the other hand, when I use the interface as return type:

```
    @strawberry.field
    def nodes(self, info) -> typing.List[NodeIface]:
        # do a dispatch to the correct type here based on DB data
        def dispatch(dbobj):
            if dbobj.is_calculation:
                return Calculation(**dbobj)
            return Node(**dbobj)
        return [dispatch(dbobj) for dbobj in db.all()]
```

I get:

```
{
  "error": {
    "data": null,
    "errors": [
      {
        "message": "Abstract type NodeIface must resolve to an Object type at runtime for field Root.nodes with value <Node instance>, received 'None'. Either the NodeIface type should provide a \"resolve_type\" function or each possible type should provide an \"is_type_of\" function.",
        "locations": [
          {
            "line": 2,
            "column": 3
          }
        ],
        "path": [
          "nodes",
          0
        ]
      }
    ]
  }
}
```

but I am a bit at a loss here on where/how to specify that information. Especially since I'm under the impression that with the typing info this should in principle work out-of-the box.


Discussion:
I've been able to reproduce the issue you're having in a local environment. 

I'm not 100% sure how interfaces are intended to work in strawberry, so I can't determine if this is a bug or expected behavior (@patrick91 can potentially provide some more information here). 

In the meantime, if you specify the return type as `typing.List[typing.Union[Calculation, Node]]` you should be able to query the information you're looking for by using an inline fragment on `NodeIface` like so:

```graphql
{
  nodes {
    __typename
    ... on NodeIface {
      uuid
    }
    ... on Calculation {
      attributes
    }
  }
}
```

Resolving patch:
diff --git a/strawberry/schema.py b/strawberry/schema.py
--- a/strawberry/schema.py
+++ b/strawberry/schema.py
@@ -7,13 +7,28 @@
 
 
 class Schema(GraphQLSchema):
-    def __init__(self, query, mutation=None, subscription=None, directives=()):
+    def __init__(
+        self, query, mutation=None, subscription=None, directives=(), types=()
+    ):
+        """
+        Wrapper around the GraphQLSchema, but compatible
+        with Strawberry types and directives.
+
+        :param query: the root query to use for the schema
+        :param mutation: the basic mutation type (if any)
+        :param mutation: the subscription type (if any)
+        :param directives: (additional) Strawberry directives
+        :param types: additional Strawberry types to register, return values of fields
+                      are automatically registered while return types for interfaces have
+                      to be manually registered
+        """
         super().__init__(
             query=query.field,
             mutation=mutation.field if mutation else None,
             subscription=subscription.field if subscription else None,
             directives=specified_directives
             + [directive.directive for directive in directives],
+            types=[type.field for type in types],
         )
 
     def __repr__(self):
diff --git a/strawberry/type.py b/strawberry/type.py
--- a/strawberry/type.py
+++ b/strawberry/type.py
@@ -79,6 +79,8 @@ def _get_fields(wrapped):
 
     extra_kwargs = {"description": description or cls.__doc__}
 
+    wrapped = dataclasses.dataclass(cls)
+
     if is_input:
         TypeClass = GraphQLInputObjectType
     elif is_interface:
@@ -92,7 +94,10 @@ def _get_fields(wrapped):
             if hasattr(klass, IS_STRAWBERRY_INTERFACE)
         ]
 
-    wrapped = dataclasses.dataclass(cls)
+        # the user might want to register other classes,
+        # but for now let's just add our dataclass
+        extra_kwargs["is_type_of"] = lambda obj, _: isinstance(obj, wrapped)
+
     wrapped.field = TypeClass(name, lambda: _get_fields(wrapped), **extra_kwargs)
 
     return wrapped


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -459,3 +459,48 @@ def uppercase(value: str, example: str):
     '''
 
     assert repr(schema) == textwrap.dedent(expected_schema).lstrip()
+
+
+def test_query_interface():
+    """Test that a query on an interface correctly detects instances of the dataclasses
+       as one of the inherited types, given that types are registered in addition to the
+       root type (since there is no field for them)"""
+
+    @strawberry.interface
+    class Cheese:
+        name: str
+
+    @strawberry.type
+    class Swiss(Cheese):
+        canton: str
+
+    @strawberry.type
+    class Italian(Cheese):
+        province: str
+
+    @strawberry.type
+    class Root:
+        @strawberry.field
+        def assortment(self, info) -> typing.List[Cheese]:
+            return [
+                Italian(name="Asiago", province="Friuli"),
+                Swiss(name="Tomme", canton="Vaud"),
+            ]
+
+    schema = strawberry.Schema(query=Root, types=[Swiss, Italian])
+
+    query = """{
+        assortment {
+            name
+            ... on Italian { province }
+            ... on Swiss { canton }
+        }
+    }"""
+
+    result = graphql_sync(schema, query)
+
+    assert not result.errors
+    assert result.data["assortment"] == [
+        {"name": "Asiago", "province": "Friuli"},
+        {"canton": "Vaud", "name": "Tomme"},
+    ]