Skip to content

Commit 5f71ac7

Browse files
authored
Merge pull request #449 from graphql-python/features/test-client-and-snapshot-testing
First version of the Graphene test client and snapshots 💪
2 parents aaf9e92 + 6c040e6 commit 5f71ac7

File tree

14 files changed

+553
-314
lines changed

14 files changed

+553
-314
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Contents:
1010
types/index
1111
execution/index
1212
relay/index
13+
testing/index
1314

1415
Integrations
1516
-----

docs/testing/index.rst

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
===================
2+
Testing in Graphene
3+
===================
4+
5+
6+
Automated testing is an extremely useful bug-killing tool for the modern developer. You can use a collection of tests – a test suite – to solve, or avoid, a number of problems:
7+
8+
- When you’re writing new code, you can use tests to validate your code works as expected.
9+
- When you’re refactoring or modifying old code, you can use tests to ensure your changes haven’t affected your application’s behavior unexpectedly.
10+
11+
Testing a GraphQL application is a complex task, because a GraphQL application is made of several layers of logic – schema definition, schema validation, permissions and field resolution.
12+
13+
With Graphene test-execution framework and assorted utilities, you can simulate GraphQL requests, execute mutations, inspect your application’s output and generally verify your code is doing what it should be doing.
14+
15+
16+
Testing tools
17+
-------------
18+
19+
Graphene provides a small set of tools that come in handy when writing tests.
20+
21+
22+
Test Client
23+
~~~~~~~~~~~
24+
25+
The test client is a Python class that acts as a dummy GraphQL client, allowing you to test your views and interact with your Graphene-powered application programmatically.
26+
27+
Some of the things you can do with the test client are:
28+
29+
- Simulate Queries and Mutations and observe the response.
30+
- Test that a given query request is rendered by a given Django template, with a template context that contains certain values.
31+
32+
33+
Overview and a quick example
34+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35+
36+
To use the test client, instantiate ``graphene.test.Client`` and retrieve GraphQL responses:
37+
38+
39+
.. code:: python
40+
41+
from graphene.test import Client
42+
43+
def test_hey():
44+
client = Client(my_schema)
45+
executed = client.execute('''{ hey }''')
46+
assert executed == {
47+
'data': {
48+
'hey': 'hello!'
49+
}
50+
}
51+
52+
53+
Execute parameters
54+
~~~~~~~~~~~~~~~~~~
55+
56+
You can also add extra keyword arguments to the ``execute`` method, such as
57+
``context_value``, ``root_value``, ``variable_values``, ...:
58+
59+
60+
.. code:: python
61+
62+
from graphene.test import Client
63+
64+
def test_hey():
65+
client = Client(my_schema)
66+
executed = client.execute('''{ hey }''', context_value={'user': 'Peter'})
67+
assert executed == {
68+
'data': {
69+
'hey': 'hello Peter!'
70+
}
71+
}
72+
73+
74+
Snapshot testing
75+
~~~~~~~~~~~~~~~~
76+
77+
As our APIs evolve, we need to know when our changes introduce any breaking changes that might break
78+
some of the clients of our GraphQL app.
79+
80+
However, writing tests and replicate the same response we expect from our GraphQL application can be
81+
tedious and repetitive task, and sometimes it's easier to skip this process.
82+
83+
Because of that, we recommend the usage of `SnapshotTest <https://github.com/syrusakbary/snapshottest/>`_.
84+
85+
SnapshotTest let us write all this tests in a breeze, as creates automatically the ``snapshots`` for us
86+
the first time the test is executed.
87+
88+
89+
Here is a simple example on how our tests will look if we use ``pytest``:
90+
91+
.. code:: python
92+
93+
def test_hey(snapshot):
94+
client = Client(my_schema)
95+
# This will create a snapshot dir and a snapshot file
96+
# the first time the test is executed, with the response
97+
# of the execution.
98+
snapshot.assert_match(client.execute('''{ hey }'''))
99+
100+
101+
If we are using ``unittest``:
102+
103+
.. code:: python
104+
105+
from snapshottest import TestCase
106+
107+
class APITestCase(TestCase):
108+
def test_api_me(self):
109+
"""Testing the API for /me"""
110+
client = Client(my_schema)
111+
self.assertMatchSnapshot(client.execute('''{ hey }'''))

examples/starwars/tests/snapshots/__init__.py

Whitespace-only changes.
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# -*- coding: utf-8 -*-
2+
# snapshottest: v1 - https://goo.gl/zC4yUc
3+
from __future__ import unicode_literals
4+
5+
from snapshottest import Snapshot
6+
7+
8+
snapshots = Snapshot()
9+
10+
snapshots['test_hero_name_query 1'] = {
11+
'data': {
12+
'hero': {
13+
'name': 'R2-D2'
14+
}
15+
}
16+
}
17+
18+
snapshots['test_hero_name_and_friends_query 1'] = {
19+
'data': {
20+
'hero': {
21+
'id': '2001',
22+
'name': 'R2-D2',
23+
'friends': [
24+
{
25+
'name': 'Luke Skywalker'
26+
},
27+
{
28+
'name': 'Han Solo'
29+
},
30+
{
31+
'name': 'Leia Organa'
32+
}
33+
]
34+
}
35+
}
36+
}
37+
38+
snapshots['test_nested_query 1'] = {
39+
'data': {
40+
'hero': {
41+
'name': 'R2-D2',
42+
'friends': [
43+
{
44+
'name': 'Luke Skywalker',
45+
'appearsIn': [
46+
'NEWHOPE',
47+
'EMPIRE',
48+
'JEDI'
49+
],
50+
'friends': [
51+
{
52+
'name': 'Han Solo'
53+
},
54+
{
55+
'name': 'Leia Organa'
56+
},
57+
{
58+
'name': 'C-3PO'
59+
},
60+
{
61+
'name': 'R2-D2'
62+
}
63+
]
64+
},
65+
{
66+
'name': 'Han Solo',
67+
'appearsIn': [
68+
'NEWHOPE',
69+
'EMPIRE',
70+
'JEDI'
71+
],
72+
'friends': [
73+
{
74+
'name': 'Luke Skywalker'
75+
},
76+
{
77+
'name': 'Leia Organa'
78+
},
79+
{
80+
'name': 'R2-D2'
81+
}
82+
]
83+
},
84+
{
85+
'name': 'Leia Organa',
86+
'appearsIn': [
87+
'NEWHOPE',
88+
'EMPIRE',
89+
'JEDI'
90+
],
91+
'friends': [
92+
{
93+
'name': 'Luke Skywalker'
94+
},
95+
{
96+
'name': 'Han Solo'
97+
},
98+
{
99+
'name': 'C-3PO'
100+
},
101+
{
102+
'name': 'R2-D2'
103+
}
104+
]
105+
}
106+
]
107+
}
108+
}
109+
}
110+
111+
snapshots['test_fetch_luke_query 1'] = {
112+
'data': {
113+
'human': {
114+
'name': 'Luke Skywalker'
115+
}
116+
}
117+
}
118+
119+
snapshots['test_fetch_some_id_query 1'] = {
120+
'data': {
121+
'human': {
122+
'name': 'Luke Skywalker'
123+
}
124+
}
125+
}
126+
127+
snapshots['test_fetch_some_id_query2 1'] = {
128+
'data': {
129+
'human': {
130+
'name': 'Han Solo'
131+
}
132+
}
133+
}
134+
135+
snapshots['test_invalid_id_query 1'] = {
136+
'data': {
137+
'human': None
138+
}
139+
}
140+
141+
snapshots['test_fetch_luke_aliased 1'] = {
142+
'data': {
143+
'luke': {
144+
'name': 'Luke Skywalker'
145+
}
146+
}
147+
}
148+
149+
snapshots['test_fetch_luke_and_leia_aliased 1'] = {
150+
'data': {
151+
'luke': {
152+
'name': 'Luke Skywalker'
153+
},
154+
'leia': {
155+
'name': 'Leia Organa'
156+
}
157+
}
158+
}
159+
160+
snapshots['test_duplicate_fields 1'] = {
161+
'data': {
162+
'luke': {
163+
'name': 'Luke Skywalker',
164+
'homePlanet': 'Tatooine'
165+
},
166+
'leia': {
167+
'name': 'Leia Organa',
168+
'homePlanet': 'Alderaan'
169+
}
170+
}
171+
}
172+
173+
snapshots['test_use_fragment 1'] = {
174+
'data': {
175+
'luke': {
176+
'name': 'Luke Skywalker',
177+
'homePlanet': 'Tatooine'
178+
},
179+
'leia': {
180+
'name': 'Leia Organa',
181+
'homePlanet': 'Alderaan'
182+
}
183+
}
184+
}
185+
186+
snapshots['test_check_type_of_r2 1'] = {
187+
'data': {
188+
'hero': {
189+
'__typename': 'Droid',
190+
'name': 'R2-D2'
191+
}
192+
}
193+
}
194+
195+
snapshots['test_check_type_of_luke 1'] = {
196+
'data': {
197+
'hero': {
198+
'__typename': 'Human',
199+
'name': 'Luke Skywalker'
200+
}
201+
}
202+
}

0 commit comments

Comments
 (0)