{"id":1110,"date":"2020-10-19T01:24:24","date_gmt":"2020-10-19T01:24:24","guid":{"rendered":"http:\/\/10.0.0.14\/?p=1110"},"modified":"2021-09-07T23:32:01","modified_gmt":"2021-09-07T23:32:01","slug":"tdd-with-django-graphene-docker-part-4","status":"publish","type":"post","link":"https:\/\/tutorials.leesonresearch.com\/tutorials\/2020\/10\/19\/tdd-with-django-graphene-docker-part-4\/","title":{"rendered":"TDD with Gatsby, Django &#038; Docker Part 1, Chapter 04 &#8212; Mutations"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Mutations<\/h2>\n\n\n\n<p>In order to post data to a server with Graphql we use a process called mutations which are similar to the query process we defined in our last post.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">TDD Graphene Mutations<\/h4>\n\n\n\n<p>We&#8217;ll begin by writing a test for creating a new todo item.<\/p>\n\n\n\n<p>Create a new file server\/todo_app\/tests\/test_graphql_mutations.py:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Double click to copy; notranslate\" title=\"Double click to copy\">\ntouch server\/todo_app\/tests\/test_graphql_mutations.py\n<\/pre><\/div>\n\n\n<p>Populate test_graphql_mutations.py with the following:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Double click to copy; notranslate\" title=\"Double click to copy\">\n# server\/todo_app\/tests\/test_graphql_mutations.py\n\nfrom graphene_django.utils.testing import GraphQLTestCase\nfrom todo_app.models import Todo, Project, Category\nimport json\n\nclass MutationTestCases(GraphQLTestCase):\n\n  def test_create_todo(self):\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    response = self.query(\n      '''\n      mutation createTodo($title: String!, $task: String!, $projId: Int!, $catId: Int!) {\n        createTodo(title: $title, task: $task, projId: $projId, catId: $catId) {\n          title\n          task\n          slug\n          project {\n            name\n            slug\n          }\n          category {\n            name\n            slug\n          }\n        }\n      }\n      ''',\n      variables = {\n        'title': 'todo',\n        'task': 'task',\n        'projId': proj1.id,\n        'catId':  cat1.id,\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\ufeff\n<\/pre><\/div>\n\n\n<h4 class=\"wp-block-heading\">Todo App Mutations Schema<\/h4>\n\n\n\n<p>Now we know we have to create a schema for this create todo mutation:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Double click to copy; notranslate\" title=\"Double click to copy\">\n# server\/todo_app\/schema.py\n\n# code ...\n\n#1\n# Mutations\nclass CreateTodo(graphene.Mutation):\n  id = graphene.Int()\n  title = graphene.String()\n  task = graphene.String()\n  is_completed = graphene.Boolean()\n  slug = graphene.String()\n  project = graphene.Field(ProjectType)\n  category = graphene.Field(CategoryType)\n\n  #2\n  class Arguments:\n    title = graphene.String()\n    task = graphene.String()\n    projId = graphene.Int()\n    catId = graphene.Int()\n\n  #3\n  def mutate(self, info, title, task, projId, catId):\n    proj = Project.objects.get(pk=projId)\n\n    cat = Category.objects.get(pk=catId)\n      \n    todo = Todo.objects.create(\n      title=title, \n      task=task, \n      project=proj, \n      category=cat,\n    )\n\n    return CreateTodo(\n      id=todo.id,\n      title=todo.title,\n      task=todo.task,\n      slug=todo.slug,\n      project=todo.project,\n      category=todo.category,\n    )\n#4\nclass Mutation(graphene.ObjectType):\n  create_todo = CreateTodo.Field()\n\nschema = Schema(query=Query, mutation=Mutation)\n<\/pre><\/div>\n\n\n<p>What is going on here?<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>#1: Define a mutation class, define the resulting output of the mutation which is the data the server sends back to the client.<\/li><li>#2: Define the arguments that the server is expecting.<\/li><li>#3: Define the mutation method that creates a new task in the database with the data the user sent. After the server returns the CreateTask class with our new data which matches the parameters in #1.<\/li><li>#4: Create a mutation class with the field to be resolved.<\/li><\/ul>\n\n\n\n<p>Your updated server\/todo_app\/schema.py should be as follows:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; highlight: [57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97]; title: Double click to copy; notranslate\" title=\"Double click to copy\">\nimport graphene\nfrom graphene import Schema\nfrom graphene_django import DjangoObjectType\nfrom graphql import GraphQLError\nfrom todo_app.models import Todo, Project, Category\n\nclass TodoType(DjangoObjectType):\n  class Meta:\n    model = Todo\n\nclass ProjectType(DjangoObjectType):\n  class Meta:\n    model = Project\n\nclass CategoryType(DjangoObjectType):\n  class Meta:\n    model = Category\n\nclass Query(graphene.ObjectType):\n  \n  # todo queries\n  todos = graphene.List(TodoType)\n  def resolve_todos(self, info, **kwargs):\n    return Todo.objects.all()\n\n  todo = graphene.Field(\n    TodoType,\n    id=graphene.Int(),\n  ) \n  def resolve_todo(self, info, id):\n    return Todo.objects.get(pk=id)\n\n  # project queries\n  projects = graphene.List(ProjectType)\n  def resolve_projects(self, info, **kwargs):\n    return Project.objects.all()\n\n  project = graphene.Field(\n    ProjectType,\n    id=graphene.Int(),\n  )\n  def resolve_project(self, info, id):\n    return Project.objects.get(pk=id)\n\n  # category queries\n  categories = graphene.List(CategoryType)\n  def resolve_categories(self, info, **kwargs):\n    return Category.objects.all()\n\n  category = graphene.Field(\n    CategoryType,\n    id=graphene.Int(),\n  )\n  def resolve_category(self, info, id):\n    return Category.objects.get(pk=id)\n\n# Mutations\nclass CreateTodo(graphene.Mutation):\n  id = graphene.Int()\n  title = graphene.String()\n  task = graphene.String()\n  is_completed = graphene.Boolean()\n  slug = graphene.String()\n  project = graphene.Field(ProjectType)\n  category = graphene.Field(CategoryType)\n \n  class Arguments:\n    title = graphene.String()\n    task = graphene.String()\n    projId = graphene.Int()\n    catId = graphene.Int()\n \n  def mutate(self, info, title, task, projId, catId):\n    proj = Project.objects.get(pk=projId)\n \n    cat = Category.objects.get(pk=catId)\n       \n    todo = Todo.objects.create(\n      title=title, \n      task=task, \n      project=proj, \n      category=cat,\n    )\n \n    return CreateTodo(\n      id=todo.id,\n      title=todo.title,\n      task=todo.task,\n      slug=todo.slug,\n      project=todo.project,\n      category=todo.category,\n    )\n\nclass Mutation(graphene.ObjectType):\n  create_todo = CreateTodo.Field()\n \nschema = Schema(query=Query, mutation=Mutation)\n<\/pre><\/div>\n\n\n<p>&#8230; update server\/todo_proj\/schema.py with our new mutation:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; highlight: [13,14,15,16,17,20]; title: Double click to copy; notranslate\" title=\"Double click to copy\">\n# server\/todo_proj\/schema.py\n\nimport graphene\n\nimport todo_app.schema\n\nclass Query(\n  todo_app.schema.Query,\n  graphene.ObjectType\n):\n  pass\n\nclass Mutation(\n  todo_app.schema.Mutation, \n  graphene.ObjectType\n):\n  pass\n\n# update the schema variable with our mutation\nschema = graphene.Schema(query=Query, mutation=Mutation)\n<\/pre><\/div>\n\n\n<p>Now we can run our tests with pytest. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Double click to copy; notranslate\" title=\"Double click to copy\">\ndocker-compose run server pytest\n<\/pre><\/div>\n\n\n<p>If all goes well we should see a listing of passed tests including our new create todo mutation test.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Double click to copy; notranslate\" title=\"Double click to copy\">\n=============================================== test session starts ===============================================\nplatform linux -- Python 3.8.5, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 -- \/usr\/local\/bin\/python\ncachedir: .pytest_cache\ndjango: settings: todo_proj.settings (from env)\nrootdir: \/var\/www\/server\nplugins: django-4.0.0\ncollected 7 items                                                                                                 \n\ntodo_app\/tests\/test_graphql_mutations.py::MutationTestCases::test_create_todo PASSED                        \ntodo_app\/tests\/test_graphql_queries.py::QueryTestCases::test_all_todos PASSED                              \ntodo_app\/tests\/test_models.py::CategoryModelTest::test_string_representation PASSED                         \ntodo_app\/tests\/test_models.py::TodoModelTest::test_string_representation PASSED                             \ntodo_app\/tests\/test_models.py::TodoModelTest::test_todo_field PASSED                                        \ntodo_app\/tests\/test_models.py::ProjectModelTest::test_project_field PASSED                                  \ntodo_app\/tests\/test_models.py::ProjectModelTest::test_string_representation PASSED                          \n<\/pre><\/div>\n\n\n<p>If your test fails you can always run pytest in verbose mode with the -vv flag:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Double click to copy; notranslate\" title=\"Double click to copy\">\ndocker-compose run server pytest -vv\n<\/pre><\/div>\n\n\n<p>In verbose mode pytest may tell you:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Double click to copy; notranslate\" title=\"Double click to copy\">\n\/usr\/local\/lib\/python3.8\/site-packages\/graphene_django\/utils\/testing.py:112: in assertResponseNoErrors\n    self.assertEqual(resp.status_code, 200)\nE   AssertionError: 400 != 200\n<\/pre><\/div>\n\n\n<p>&#8230; which is not much, other than your mutation has an error <em>somewhere<\/em> in the code. In which case you can add another level of granularity to our mutation test using assert content == to an expected result. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; highlight: [42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,61]; title: Double click to copy; notranslate\" title=\"Double click to copy\">\n# server\/todo_app\/tests\/test_graphql_mutations.py\n\nfrom graphene_django.utils.testing import GraphQLTestCase\nfrom todo_app.models import Todo, Project, Category\nimport json\n\nclass MutationTestCases(GraphQLTestCase):\n\n  def test_create_todo(self):\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    response = self.query(\n      '''\n      mutation createTodo($title: String!, $task: String!, $projId: Int!, $catId: Int!) {\n        createTodo(title: $title, task: $task, projId: $projId, catId: $catId) {\n          title\n          task\n          slug\n          project {\n            name\n            slug\n          }\n          category {\n            name\n            slug\n          }\n        }\n      }\n      ''',\n      variables = {\n        'title': 'todo',\n        'task': 'task',\n        'projId': proj1.id,\n        'catId':  cat1.id,\n      }\n    )\n\n    expected = {\n      'data': {\n        'createTodo': {\n          'title': 'todo',\n          'task': 'task',\n          'slug': 'todo',\n          'project': {\n            'name': 'proj1',\n            'slug': 'proj1',\n          },\n          'category': {\n            'name': 'cat1',\n            'slug': 'cat1',\n          }\n        }\n      }\n    }\n\n    content = json.loads(response.content)\n    assert content ==  expected\n    self.assertResponseNoErrors(response)\n<\/pre><\/div>\n\n\n<p>I will go this extra mile if I do get the AssertionError: 400 != 200 error in which case the assert content == expected proves most helpful in tracking down the problem in the verbose test result.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Complete Schema &amp; Tests for All Crud Operations<\/h4>\n\n\n\n<p>We have the create operation for creating a todo but, now we need update and delete operations for both the schema and mutation tests which following in the same vein as our create todo mutation.<\/p>\n\n\n\n<p>We also need to extend our schema and tests for all the crud operations for the Project and Category models.<\/p>\n\n\n\n<p>Again we&#8217;ll start with tests. Below are tests for the crud operations of all our models:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; highlight: [64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644]; title: Double click to copy; notranslate\" title=\"Double click to copy\">\n# server\/todo_app\/tests\/test_graphql_mutations.py\n\nfrom graphene_django.utils.testing import GraphQLTestCase\nfrom todo_app.models import Todo, Project, Category\nimport json\n\nclass MutationTestCases(GraphQLTestCase):\n\n  def test_create_todo(self):\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    response = self.query(\n      '''\n      mutation createTodo($title: String!, $task: String!, $projId: Int!, $catId: Int!) {\n        createTodo(title: $title, task: $task, projId: $projId, catId: $catId) {\n          title\n          task\n          slug\n          project {\n            name\n            slug\n          }\n          category {\n            name\n            slug\n          }\n        }\n      }\n      ''',\n      variables = {\n        'title': 'todo',\n        'task': 'task',\n        'projId': proj1.id,\n        'catId':  cat1.id,\n      }\n    )\n\n    expected = {\n      'data': {\n        'createTodo': {\n          'title': 'todo',\n          'task': 'task',\n          'slug': 'todo',\n          'project': {\n            'name': 'proj1',\n            'slug': 'proj1',\n          },\n          'category': {\n            'name': 'cat1',\n            'slug': 'cat1',\n          }\n        }\n      }\n    }\n\n    content = json.loads(response.content)\n    assert content ==  expected\n    self.assertResponseNoErrors(response)\n\n  def test_update_todo_name(self):\n    # Test for updating a todos name\n\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    todo = Todo(title='todo', task='todo task', project=proj1, category=cat1)\n    todo.save()\n\n    response = self.query(\n      '''\n      mutation updateTodo($id: Int!, $title: String!) {\n        updateTodo(id: $id, title: $title){\n          id\n          slug\n          title\n          task\n          project {\n            slug\n            name\n          }\n          category {\n            slug\n            name\n            parent {\n              slug\n              name\n            }\n          }\n        }\n      }\n\n      ''',\n        variables = {\n          'id': todo.id,\n          'title': 'todo updated',\n        }\n      )\n\n    content = json.loads(response.content)\n    #assert content == {}\n    self.assertResponseNoErrors(response)\n\n  def test_update_todo_task(self):\n    # Test for updating a todos task\n\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    todo = Todo(title='todo', task='todo task', project=proj1, category=cat1)\n    todo.save()\n\n    response = self.query(\n        '''\n        mutation updateTodo($id: Int!, $task: String!) {\n          updateTodo(id: $id, task: $task){\n            id\n            slug\n            title\n            task\n            project {\n              slug\n              name\n            }\n            category {\n              slug\n              name\n              parent {\n                slug\n                name\n              }\n            }\n          }\n        }\n\n        ''',\n          variables = {\n            'id': todo.id,\n            'task': 'todo updated task',\n          }\n        )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_todo_project(self):\n    # Test for updating a todos project\n\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    todo = Todo(title='todo', task='todo task', project=proj1, category=cat1)\n    todo.save()\n\n    proj2 = Project(name='proj2')\n    proj2.save()\n\n    response = self.query(\n        '''\n        mutation updateTodo($id: Int!, $projId: Int!) {\n          updateTodo(id: $id, projId: $projId){\n            id\n            slug\n            title\n            task\n            project {\n              slug\n              name\n            }\n            category {\n              slug\n              name\n              parent {\n                slug\n                name\n              }\n            }\n          }\n        }\n\n        ''',\n          variables = {\n            'id': todo.id,\n            'projId': proj2.id,\n          }\n        )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_todo_category(self):\n    # Test for updating a todos category\n\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    todo = Todo(title='todo', task='todo task', project=proj1, category=cat1)\n    todo.save()\n\n    cat2 = Category(name='cat2')\n    cat2.save()\n\n    response = self.query(\n        '''\n        mutation updateTodo($id: Int!, $catId: Int!) {\n          updateTodo(id: $id, catId: $catId){\n            id\n            slug\n            title\n            task\n            project {\n              slug\n              name\n            }\n            category {\n              slug\n              name\n              parent {\n                slug\n                name\n              }\n            }\n          }\n        }\n\n        ''',\n          variables = {\n            'id': todo.id,\n            'catId': cat2.id,\n          }\n        )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_todo_is_completed(self):\n    # Test for updating a todos is_completed status\n\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    todo = Todo(title='todo', task='todo task', project=proj1, category=cat1)\n    todo.save()\n\n\n    response = self.query(\n        '''\n        mutation updateTodo($id: Int!, $isCompleted: Boolean!) {\n          updateTodo(id: $id, isCompleted: $isCompleted){\n            id\n            slug\n            title\n            task\n            isCompleted\n            project {\n              slug\n              name\n            }\n            category {\n              slug\n              name\n              parent {\n                slug\n                name\n              }\n            }\n          }\n        }\n\n        ''',\n          variables = {\n            'id': todo.id,\n            'isCompleted': True,\n          }\n        )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_todo_everything(self):\n    # Test for updating all of a todo's attributes at once\n\n    proj1 = Project(name='proj1')\n    proj1.save()\n\n    cat1 = Category(name='cat1')\n    cat1.save()\n\n    proj2 = Project(name='proj2')\n    proj2.save()\n\n    cat2 = Category(name='cat2')\n    cat2.save()\n\n    todo = Todo(title='todo', task='todo task', project=proj1, category=cat1)\n    todo.save()\n\n\n    response = self.query(\n        '''\n        mutation updateTodo(\n          $id: Int!,\n          $title: String!,\n          $task: String!,\n          $projId: Int!,\n          $catId: Int!,\n          $isCompleted: Boolean!\n          ) {\n          updateTodo(\n            id: $id,\n            title: $title,\n            task: $task,\n            projId: $projId,\n            catId: $catId,\n            isCompleted: $isCompleted,\n            ){\n            id\n            slug\n            title\n            task\n            isCompleted\n            project {\n              slug\n              name\n            }\n            category {\n              slug\n              name\n              parent {\n                slug\n                name\n              }\n            }\n          }\n        }\n\n        ''',\n          variables = {\n            'id': todo.id,\n            'title': 'updated title',\n            'task': 'updated task',\n            'projId': proj2.id,\n            'catId': cat2.id,\n            'isCompleted': True,\n          }\n        )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_delete_todo(self):\n    todo = Todo.objects.create(title='todo')\n\n    response = self.query(\n      '''\n      mutation DeleteTodo($id: Int!) {\n        deleteTodo(id: $id) {\n          id\n          title\n        }\n      }\n      ''',\n      variables = {\n        'id': todo.id\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_create_project(self):\n    response = self.query(\n      '''\n      mutation createProject($name: String!, $description: String!) {\n        createProject(name: $name, description: $description) {\n          id\n          name\n          description\n          slug\n        }\n      }\n      ''',\n      variables = {\n        'name': 'My Project',\n        'description': 'Description for my project',\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_delete_project(self):\n    project = Project.objects.create(name='foo')\n\n    response = self.query(\n      '''\n      mutation deleteProject($id: Int!) {\n        deleteProject(id: $id) {\n          id\n          name\n        }\n      }\n      ''',\n      variables = {\n        'id': project.id,\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_project_name(self):\n    project = Project.objects.create(name='foo')\n    # Test updating both name of a project\n\n    response = self.query(\n      '''\n      mutation updateProject($id: Int!, $name: String!) {\n        updateProject(id:$id, name: $name) {\n          id\n          name\n          description\n          slug\n        }\n      }\n      ''',\n      variables = {\n        'id': project.id,\n        'name': 'My Project',\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_project_name_description(self):\n    project = Project.objects.create(name='foo')\n    # Test updating both name and description of a project\n\n    response = self.query(\n      '''\n      mutation updateProject($id: Int!, $name: String!, $description: String!) {\n        updateProject(id:$id, name: $name, description: $description) {\n          id\n          name\n          description\n          slug\n        }\n      }\n      ''',\n      variables = {\n        'id': project.id,\n        'name': 'My Project',\n        'description': 'Description for my project',\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_project_description(self):\n    # Test updating of project description\n    project = Project.objects.create(name='foo', description='bar')\n\n    response = self.query(\n      '''\n      mutation updateProject($id: Int!, $description: String!) {\n        updateProject(id:$id, description: $description) {\n          id\n          name\n          description\n          slug\n        }\n      }\n      ''',\n      variables = {\n        'id': project.id,\n        'description': 'Description for my project',\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_create_category_no_parent(self):\n    response = self.query(\n      '''\n      mutation createCategory($name: String!) {\n        createCategory(name: $name) {\n          id\n          name\n          slug\n        }\n      }\n      ''',\n      variables = {\n        'name': 'my category',\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_create_category_with_parent(self):\n    parent = Category.objects.create(name='Category Parent')\n\n    response = self.query(\n      '''\n      mutation createCategory($name: String!, $parentId: Int!) {\n        createCategory(name: $name, parentId: $parentId) {\n          id\n          name\n          slug\n        }\n      }\n      ''',\n      variables = {\n        'name': 'my category',\n        'parentId': parent.id\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_delete_category(self):\n    category = Category.objects.create(name='foo')\n\n    response = self.query(\n      '''\n      mutation deleteCategory($id: Int!) {\n        deleteCategory(id: $id) {\n          id\n          name\n        }\n      }\n      ''',\n      variables = {\n        'id': category.id,\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_category_name(self):\n    category = Category.objects.create(name='foo')\n    # Test for changing a category's name\n\n    response = self.query(\n      '''\n      mutation updateCategory($id: Int!,$name: String!){\n        updateCategory(id: $id, name: $name) {\n          id\n          name\n          slug\n        }\n      }\n      ''',\n      variables = {\n        'id': category.id,\n        'name': 'New Category Name',\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_category_parent(self):\n    categoryParent = Category.objects.create(name='Parent Category')\n    category = Category.objects.create(name='foo')\n    # Test for moving category into a parent category.\n\n    response = self.query(\n      '''\n      mutation updateCategory($id: Int!,$parentId: Int!){\n        updateCategory(id: $id, parentId: $parentId) {\n          id\n          name\n          slug\n          parent {\n            id\n            name\n            slug\n          }\n        }\n      }\n      ''',\n      variables = {\n        'id': category.id,\n        'parentId': categoryParent.id,\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n\n  def test_update_category_name_parent(self):\n    categoryParent = Category.objects.create(name='category parent')\n    category = Category.objects.create(name='foobar')\n    # Test for changing a category's name &amp;amp; moving renamed category into a parent category\n\n    response = self.query(\n      '''\n      mutation updateCategory($id: Int!, $name: String!, $parentId: Int!){\n        updateCategory(id: $id, name: $name, parentId: $parentId) {\n          id\n          name\n          slug\n          parent {\n            id\n            name\n            slug\n          }\n        }\n      }\n      ''',\n      variables = {\n        'id': category.id,\n        'name': 'New Category Name',\n        'parentId': categoryParent.id\n      }\n    )\n\n    content = json.loads(response.content)\n    self.assertResponseNoErrors(response)\n<\/pre><\/div>\n\n\n<p>Running these tests will throw multiple errors which clarifies our thinking in the sense that pytest is telling us what object types, fields and resolvers we need to add to our schema.<\/p>\n\n\n\n<p>The following is a complete update of the todo app schema which should enable us to pass all our tests:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; highlight: [96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,327,328,329,330,331,332,333,334]; title: Double click to copy; notranslate\" title=\"Double click to copy\">\n# server\/todo_app\/schema.py\n\nimport graphene\nfrom graphene import Schema\nfrom graphene_django import DjangoObjectType\nfrom graphql import GraphQLError\nfrom todo_app.models import Todo, Project, Category\n\nclass TodoType(DjangoObjectType):\n  class Meta:\n    model = Todo\n\nclass ProjectType(DjangoObjectType):\n  class Meta:\n    model = Project\n\nclass CategoryType(DjangoObjectType):\n  class Meta:\n    model = Category\n\nclass Query(graphene.ObjectType):\n  \n  # todo queries\n  todos = graphene.List(TodoType)\n  def resolve_todos(self, info, **kwargs):\n    return Todo.objects.all()\n\n  todo = graphene.Field(\n    TodoType,\n    id=graphene.Int(),\n  ) \n  def resolve_todo(self, info, id):\n    return Todo.objects.get(pk=id)\n\n  # project queries\n  projects = graphene.List(ProjectType)\n  def resolve_projects(self, info, **kwargs):\n    return Project.objects.all()\n\n  project = graphene.Field(\n    ProjectType,\n    id=graphene.Int(),\n  )\n  def resolve_project(self, info, id):\n    return Project.objects.get(pk=id)\n\n  # category queries\n  categories = graphene.List(CategoryType)\n  def resolve_categories(self, info, **kwargs):\n    return Category.objects.all()\n\n  category = graphene.Field(\n    CategoryType,\n    id=graphene.Int(),\n  )\n  def resolve_category(self, info, id):\n    return Category.objects.get(pk=id)\n\n# Mutations\nclass CreateTodo(graphene.Mutation):\n  id = graphene.Int()\n  title = graphene.String()\n  task = graphene.String()\n  is_completed = graphene.Boolean()\n  slug = graphene.String()\n  project = graphene.Field(ProjectType)\n  category = graphene.Field(CategoryType)\n\n  class Arguments:\n    title = graphene.String()\n    task = graphene.String()\n    projId = graphene.Int()\n    catId = graphene.Int()\n\n  def mutate(self, info, title, task, projId, catId):\n    proj = Project.objects.get(pk=projId)\n\n    cat = Category.objects.get(pk=catId)\n      \n    todo = Todo.objects.create(\n      title=title, \n      task=task, \n      project=proj, \n      category=cat,\n    )\n\n    return CreateTodo(\n      id=todo.id,\n      title=todo.title,\n      task=todo.task,\n      slug=todo.slug,\n      project=todo.project,\n      category=todo.category,\n    )\n\nclass DeleteTodo(graphene.Mutation):\n  id = graphene.Int()\n  title = graphene.String()\n  ok = graphene.Boolean()\n\n  class Arguments:\n    id = graphene.Int()\n\n  def mutate(self, info, id=id):\n    todo = Todo.objects.get(pk=id)\n\n    if todo:\n      todo.delete()\n      return DeleteTodo(\n        id = todo.id,\n        title = todo.title,\n        ok = True\n      )\n\nclass UpdateTodo(graphene.Mutation):\n  id = graphene.Int()\n  title = graphene.String()\n  task = graphene.String()\n  is_completed = graphene.Boolean()\n  project = graphene.Field(ProjectType)\n  category = graphene.Field(CategoryType)\n  slug = graphene.String()\n\n  class Arguments:\n    id = graphene.Int()\n    title = graphene.String()\n    task = graphene.String()\n    is_completed = graphene.Boolean()\n    projId = graphene.Int()\n    catId = graphene.Int()\n\n  def mutate(self, info, id, title=None, task=None, is_completed=None, projId=None, catId=None):\n    todo = Todo.objects.get(pk=id)\n    if projId:\n      project = Project.objects.get(pk=projId)\n      if not project:\n        raise Exception('There is no project with this id.')\n\n    if catId:\n      category = Category.objects.get(pk=catId)\n      if not category:\n        raise Exception('No category with this id exists.')\n\n    if todo:\n      if title:\n        todo.title = title\n      if task:\n        todo.task = task\n      if is_completed:\n        todo.is_completed = is_completed\n      if projId:\n        todo.project = project\n      if catId:\n        todo.category = category\n\n      todo.save()\n\n      return UpdateTodo(\n        id = todo.id,\n        title = todo.title,\n        task = todo.task,\n        is_completed = todo.is_completed,\n        project = todo.project,\n        category = todo.category,\n        slug = todo.slug,\n      )\n    else:\n      raise Exception('There is no todo with this id.')\n\nclass CreateProject(graphene.Mutation):\n  id = graphene.Int()\n  name = graphene.String()\n  description = graphene.String()\n  slug = graphene.String()\n\n  class Arguments:\n    name = graphene.String()\n    description = graphene.String()\n\n  def mutate(self, info, name, description=None):\n    project = Project.objects.create(name=name, description=description)\n\n    return CreateProject(\n      id = project.id,\n      name = project.name,\n      description = project.description,\n      slug = project.slug\n    )\n\nclass DeleteProject(graphene.Mutation):\n  id = graphene.Int()\n  name = graphene.String()\n  ok = graphene.Boolean()\n\n  class Arguments:\n    id = graphene.Int()\n\n  def mutate(self, info, id=id):\n    project = Project.objects.get(pk=id)\n\n    if project:\n      project.delete()\n      return DeleteProject(\n        id = project.id,\n        name = project.name,\n        ok = True,\n      )\n\nclass UpdateProject(graphene.Mutation):\n  id = graphene.Int()\n  name = graphene.String()\n  description = graphene.String()\n  slug = graphene.String()\n\n  class Arguments:\n    id = graphene.Int()\n    name = graphene.String()\n    description = graphene.String()\n  \n  def mutate(self, info, id=id, name=None, description=None):\n    project = Project.objects.get(pk=id)\n\n    if project:\n      if name:\n        project.name = name\n      if description:\n        project.description = description\n\n      project.save()\n\n      return UpdateProject(\n        id = project.id,\n        name = project.name,\n        description = project.description,\n        slug = project.slug, \n      )\n    else:\n      raise Exception('There is no project with this id.')\n\nclass CreateCategory(graphene.Mutation):\n  id = graphene.Int()\n  name = graphene.String()\n  slug = graphene.String()\n  parent = graphene.Field(CategoryType)\n\n  class Arguments:\n    name = graphene.String()\n    parentId = graphene.Int()\n\n  def mutate(self, info, name, parentId=None):\n\n    if parentId:\n      parent = Category.objects.get(pk=parentId)\n      category = Category.objects.create(name=name, parent=parent)\n    else:\n      category = Category.objects.create(name=name)\n\n    return CreateCategory(\n      id=category.id,\n      name=category.name,\n      slug=category.slug,\n      parent=category.parent,\n    )\n\nclass DeleteCategory(graphene.Mutation):\n  id = graphene.Int()\n  name = graphene.String()\n  slug = graphene.String()\n  ok = graphene.Boolean()\n\n  class Arguments:\n    id = graphene.Int()\n\n  def mutate(self, info, id=id):\n\n    category = Category.objects.get(pk=id)\n    if category:\n      category.delete()\n      return DeleteCategory(\n        id = category.id,\n        name = category.name,\n        slug = category.slug,\n        ok = True\n      )\n\nclass UpdateCategory(graphene.Mutation):\n  id = graphene.Int()\n  name = graphene.String()\n  parent = graphene.Field(CategoryType)\n  slug = graphene.String()\n\n  class Arguments:\n    id = graphene.Int()\n    name = graphene.String()\n    parentId = graphene.Int()\n\n  def mutate(self, info, id, name=None, parentId=None):\n    category = Category.objects.get(pk=id)\n\n    if parentId:\n      parent = Category.objects.get(pk=parentId)\n      if not parent:\n        raise Exception('No category with this id to be used as the updated parent.')\n\n\n    if category:\n      if name:\n        category.name = name\n      if parentId:\n        category.parent = parent\n      \n      category.save()\n\n      return UpdateCategory(\n        id = category.id,\n        name = category.name,\n        parent = category.parent,\n        slug = category.slug,\n      )\n    else:\n      raise Exception('No category with this id.')      \n\n\n\nclass Mutation(graphene.ObjectType):\n  create_todo = CreateTodo.Field()\n  update_todo = UpdateTodo.Field()\n  delete_todo = DeleteTodo.Field()\n  create_project = CreateProject.Field()\n  delete_project = DeleteProject.Field()\n  update_project = UpdateProject.Field()\n  create_category = CreateCategory.Field()\n  delete_category = DeleteCategory.Field()\n  update_category = UpdateCategory.Field()\n\nschema = Schema(query=Query, mutation=Mutation)\n<\/pre><\/div>\n\n\n<p>Running my tests&#8230;<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: Double click to copy; notranslate\" title=\"Double click to copy\">\ndocker-compose run server pytest\n<\/pre><\/div>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"298\" src=\"http:\/\/10.0.0.14\/wp-content\/uploads\/2020\/10\/pytest_result-1024x298.png\" alt=\"\" class=\"wp-image-1143\" srcset=\"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-content\/uploads\/2020\/10\/pytest_result-1024x298.png 1024w, https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-content\/uploads\/2020\/10\/pytest_result-300x87.png 300w, https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-content\/uploads\/2020\/10\/pytest_result-768x223.png 768w, https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-content\/uploads\/2020\/10\/pytest_result-1536x447.png 1536w, https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-content\/uploads\/2020\/10\/pytest_result-2048x595.png 2048w, https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-content\/uploads\/2020\/10\/pytest_result-600x174.png 600w, https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-content\/uploads\/2020\/10\/pytest_result-945x275.png 945w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>&#8230; all 30 of my tests passed. This gives me enormous confidence moving forward with this project.<\/p>\n\n\n\n<p>Next post we add java web token, JWT, user authentication allowing only an authenticated user to create, edit and delete their project todos.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link\" href=\"tdd-with-django-graphene-docker-part-3\">&lt; Previous: Chapter 3<\/a><\/div>\n\n\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link\" href=\"tdd-with-django-graphene-docker-part-5-jwt\">Next: Chapter 5 &gt;<\/a><\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Mutations In order to post data to a server with Graphql we use a process called mutations which are similar to the query process we defined in our last post. TDD Graphene Mutations We&#8217;ll begin by writing a test for&#8230; <a class=\"more-link\" href=\"https:\/\/tutorials.leesonresearch.com\/tutorials\/2020\/10\/19\/tdd-with-django-graphene-docker-part-4\/\">Continue Reading &rarr;<\/a><\/p>\n","protected":false},"author":1,"featured_media":312,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16,20],"tags":[15,44,45,29],"class_list":["post-1110","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-django","category-featured-tutorial","tag-django","tag-mutations","tag-pytest","tag-tdd"],"_links":{"self":[{"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/posts\/1110","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/comments?post=1110"}],"version-history":[{"count":53,"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/posts\/1110\/revisions"}],"predecessor-version":[{"id":2507,"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/posts\/1110\/revisions\/2507"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/media\/312"}],"wp:attachment":[{"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/media?parent=1110"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/categories?post=1110"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tutorials.leesonresearch.com\/tutorials\/wp-json\/wp\/v2\/tags?post=1110"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}