项目作者: JacekKosciesza

项目描述 :
GraphQL 'Star Wars' example using GraphQL for .NET, ASP.NET Core, Entity Framework Core
高级语言: C#
项目地址: git://github.com/JacekKosciesza/StarWars.git
创建时间: 2017-02-12T16:01:26Z
项目社区:https://github.com/JacekKosciesza/StarWars

开源协议:MIT License

下载


GraphQL ‘Star Wars’ example using GraphQL for .NET, ASP.NET Core, Entity Framework Core

Build Status

Examples

Roadmap

  • Basic
    • Simple tutorial (step/screenshot/code)
    • Detailed tutorial (steps explanation)
    • 3-Layers (Api, Core, Data) architecture
    • DDD (Domain Driven Design) hexagonal architecture
    • Dependency Inversion (deafult ASP.NET Core IoC container)
    • GraphQL controller
    • In Memory ‘Droid’ Repository
    • Entity Framework ‘Droid’ Repository
    • Automatic database creation
    • Seed database data
    • EF Migrations
    • GraphiQL
    • Unit Tests
    • Visual Studio 2017 RC upgrade
    • Integration Tests
    • Logs
    • Code Coverage
    • Continous Integration
  • Advanced
    • Full ‘Star Wars’ database (Episodes, Characters, Planets, Humans etc.)
    • Base/generic repository
    • Visual Studio 2017 RTM upgrade
    • Repositories
    • GraphQL queries
    • GraphQL mutations
    • Docker
  • PWA (Progressive Web App)
    • Identity microservice
    • Angular frontend
    • Apollo GraphQL Client for Angular
    • Service Worker
    • IndexedDB

Tutorials

Basic

  • Create ‘StarWars’ empty solution
    empty-solution

  • Add ‘ASP.NET Core Web Application (.NET Core)’ project named ‘StarWars.Api’
    aspnet-core-web-application

  • Select Web API template
    aspnet-core-web-api

  • Update all NuGet packages
    update-all-nuget-packages

  • Update project.json with correct runtime

    1. "runtimes": {
    2. "win10-x64": { }
    3. }
  • Install GraphQL NuGet package
    install-graphql-nuget-package

  • Create ‘StarWars.Core’ project
    starwars-core-project

  • Create ‘Droid’ model

    1. namespace StarWars.Core.Models
    2. {
    3. public class Droid
    4. {
    5. public int Id { get; set; }
    6. public string Name { get; set; }
    7. }
    8. }
  • Create ‘DroidType’ model
    ```csharp
    using GraphQL.Types;
    using StarWars.Core.Models;

namespace StarWars.Api.Models
{
public class DroidType : ObjectGraphType
{
public DroidType()
{
Field(x => x.Id).Description(“The Id of the Droid.”);
Field(x => x.Name, nullable: true).Description(“The name of the Droid.”);
}
}
}

  1. * Create 'StarWarsQuery' model
  2. ```csharp
  3. using GraphQL.Types;
  4. using StarWars.Core.Models;
  5. namespace StarWars.Api.Models
  6. {
  7. public class StarWarsQuery : ObjectGraphType
  8. {
  9. public StarWarsQuery()
  10. {
  11. Field<DroidType>(
  12. "hero",
  13. resolve: context => new Droid { Id = 1, Name = "R2-D2" }
  14. );
  15. }
  16. }
  17. }
  • Create ‘GraphQLQuery’ model

    1. namespace StarWars.Api.Models
    2. {
    3. public class GraphQLQuery
    4. {
    5. public string OperationName { get; set; }
    6. public string NamedQuery { get; set; }
    7. public string Query { get; set; }
    8. public string Variables { get; set; }
    9. }
    10. }
  • Create ‘GraphQLController’
    ```csharp
    using GraphQL;
    using GraphQL.Types;
    using Microsoft.AspNetCore.Mvc;
    using StarWars.Api.Models;
    using System.Threading.Tasks;

namespace StarWars.Api.Controllers
{
[Route(“graphql”)]
public class GraphQLController : Controller
{
[HttpPost]
public async Task Post([FromBody] GraphQLQuery query)
{
var schema = new Schema { Query = new StarWarsQuery() };

  1. var result = await new DocumentExecuter().ExecuteAsync(_ =>
  2. {
  3. _.Schema = schema;
  4. _.Query = query.Query;
  5. }).ConfigureAwait(false);
  6. if (result.Errors?.Count > 0)
  7. {
  8. return BadRequest();
  9. }
  10. return Ok(result);
  11. }
  12. }

}

  1. * Test using Postman
  2. ![postman-test-query](https://cloud.githubusercontent.com/assets/8171434/22866705/17985b54-f17b-11e6-848c-6482b45e4934.png)
  3. * Create 'IDroidRepository' interface
  4. ```csharp
  5. using StarWars.Core.Models;
  6. using System.Threading.Tasks;
  7. namespace StarWars.Core.Data
  8. {
  9. public interface IDroidRepository
  10. {
  11. Task<Droid> Get(int id);
  12. }
  13. }
  • Create ‘StarWars.Data’ project
    starwars-data-project

  • Create in memory ‘DroidRepository’
    ```csharp
    using StarWars.Core.Data;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using StarWars.Core.Models;
    using System.Linq;

namespace StarWars.Data.InMemory
{
public class DroidRepository : IDroidRepository
{
private List _droids = new List {
new Droid { Id = 1, Name = “R2-D2” }
};

  1. public Task<Droid> Get(int id)
  2. {
  3. return Task.FromResult(_droids.FirstOrDefault(droid => droid.Id == id));
  4. }
  5. }

}

  1. * Use 'IDroidRepository' in StarWarsQuery
  2. ```csharp
  3. using GraphQL.Types;
  4. using StarWars.Core.Data;
  5. namespace StarWars.Api.Models
  6. {
  7. public class StarWarsQuery : ObjectGraphType
  8. {
  9. private IDroidRepository _droidRepository { get; set; }
  10. public StarWarsQuery(IDroidRepository _droidRepository)
  11. {
  12. Field<DroidType>(
  13. "hero",
  14. resolve: context => _droidRepository.Get(1)
  15. );
  16. }
  17. }
  18. }
  • Update creation of StarWarsQuery in GraphQLController

    1. // ...
    2. public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
    3. {
    4. var schema = new Schema { Query = new StarWarsQuery(new DroidRepository()) };
    5. // ...
  • Test using Postman

  • Configure dependency injection in Startup.cs

    1. // ...
    2. public void ConfigureServices(IServiceCollection services)
    3. {
    4. services.AddMvc();
    5. services.AddTransient<StarWarsQuery>();
    6. services.AddTransient<IDroidRepository, DroidRepository>();
    7. }
    8. // ...
  • Use constructor injection of StarWarsQuery in GraphQLController

    1. // ...
    2. public class GraphQLController : Controller
    3. {
    4. private StarWarsQuery _starWarsQuery { get; set; }
    5. public GraphQLController(StarWarsQuery starWarsQuery)
    6. {
    7. _starWarsQuery = starWarsQuery;
    8. }
    9. [HttpPost]
    10. public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
    11. {
    12. var schema = new Schema { Query = _starWarsQuery };
    13. // ...
  • Add Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.SqlServer Nuget packages to StarWars.Data project
    entity-framework-core-nuget
    entity-framework-sql-server-provider-nuget

  • Create StarWarsContext
    ```csharp
    using Microsoft.EntityFrameworkCore;
    using StarWars.Core.Models;

namespace StarWars.Data.EntityFramework
{
public class StarWarsContext : DbContext
{
public StarWarsContext(DbContextOptions options)
: base(options)
{
Database.EnsureCreated();
}

  1. public DbSet<Droid> Droids { get; set; }
  2. }

}

  1. * Update 'appsetting.json' with database connection
  2. ```json
  3. {
  4. "Logging": {
  5. "IncludeScopes": false,
  6. "LogLevel": {
  7. "Default": "Debug",
  8. "System": "Information",
  9. "Microsoft": "Information"
  10. }
  11. },
  12. "ConnectionStrings": {
  13. "StarWarsDatabaseConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=StarWars;Integrated Security=SSPI;integrated security=true;MultipleActiveResultSets=True;"
  14. }
  15. }
  • Create EF droid repository
    ```csharp
    using StarWars.Core.Data;
    using System.Threading.Tasks;
    using StarWars.Core.Models;
    using Microsoft.EntityFrameworkCore;

namespace StarWars.Data.EntityFramework.Repositories
{
public class DroidRepository : IDroidRepository
{
private StarWarsContext _db { get; set; }

  1. public DroidRepository(StarWarsContext db)
  2. {
  3. _db = db;
  4. }
  5. public Task<Droid> Get(int id)
  6. {
  7. return _db.Droids.FirstOrDefaultAsync(droid => droid.Id == id);
  8. }
  9. }

}

  1. * Create seed data as an extension to StarWarsContext
  2. ```csharp
  3. using StarWars.Core.Models;
  4. using System.Linq;
  5. namespace StarWars.Data.EntityFramework.Seed
  6. {
  7. public static class StarWarsSeedData
  8. {
  9. public static void EnsureSeedData(this StarWarsContext db)
  10. {
  11. if (!db.Droids.Any())
  12. {
  13. var droid = new Droid
  14. {
  15. Name = "R2-D2"
  16. };
  17. db.Droids.Add(droid);
  18. db.SaveChanges();
  19. }
  20. }
  21. }
  22. }
  • Configure dependency injection and run data seed in Startup.cs
    ```csharp
    // …
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddMvc();

    services.AddTransient();
    services.AddTransient();
    services.AddDbContext(options =>

    1. options.UseSqlServer(Configuration["ConnectionStrings:StarWarsDatabaseConnection"])

    );
    }

public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory, StarWarsContext db)
{
loggerFactory.AddConsole(Configuration.GetSection(“Logging”));
loggerFactory.AddDebug();

  1. app.UseMvc();
  2. db.EnsureSeedData();

}
// …

  1. * Run application and make sure database is created
  2. ![ssms-starwars-database-created](https://cloud.githubusercontent.com/assets/8171434/22923521/1fb046da-f2a2-11e6-8e56-c00661e27318.png)
  3. * Final test using Postman
  4. ![postman-test-query](https://cloud.githubusercontent.com/assets/8171434/22866705/17985b54-f17b-11e6-848c-6482b45e4934.png)
  5. #### Entity Framework Migrations
  6. * Add 'Microsoft.EntityFrameworkCore.Design' NuGet package to 'StarWars.Data' project
  7. ![ef-design-nuget](https://cloud.githubusercontent.com/assets/8171434/22964859/fde4e42a-f35a-11e6-89ad-1b5dfeda4e67.png)
  8. * Add 'Microsoft.EntityFrameworkCore.Tools.DotNet' NuGet package to 'StarWars.Data' project
  9. ![ef-tools-dotnet-nuget](https://cloud.githubusercontent.com/assets/8171434/22964861/ff9afdf4-f35a-11e6-8e42-87ed30009877.png)
  10. * Add tools section in project.json (StarWars.Data)
  11. ```json
  12. "tools": {
  13. "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.1.0-preview4-final"
  14. }
  • Add official workaround for problems with targeting class library (Modify your class library to be a startup application)
    • Add main entry point
      1. namespace StarWars.Data.EntityFramework.Workaround
      2. {
      3. // WORKAROUND: https://docs.efproject.net/en/latest/miscellaneous/cli/dotnet.html#targeting-class-library-projects-is-not-supported
      4. public static class Program
      5. {
      6. public static void Main() { }
      7. }
      8. }
    • Add build option in project.json
      1. "buildOptions": {
      2. "emitEntryPoint": true
      3. }
  • Run migrations command from the console
    1. dotnet ef migrations add Inital -o .\EntityFramework\Migrations
    dotnet-ef-migrations

GrahpiQL

  • Add NPM configuration file ‘package.json’ to StarWars.Api project
    npm-configuration-file

  • Add GraphiQL dependencies and webpack bundle task

    1. {
    2. "version": "1.0.0",
    3. "name": "starwars-graphiql",
    4. "private": true,
    5. "scripts": {
    6. "start": "webpack --progress"
    7. },
    8. "dependencies": {
    9. "graphiql": "^0.7.8",
    10. "graphql": "^0.7.0",
    11. "isomorphic-fetch": "^2.1.1",
    12. "react": "^15.3.1",
    13. "react-dom": "^15.3.1"
    14. },
    15. "devDependencies": {
    16. "babel": "^5.6.14",
    17. "babel-loader": "^5.3.2",
    18. "css-loader": "^0.24.0",
    19. "extract-text-webpack-plugin": "^1.0.1",
    20. "postcss-loader": "^0.10.1",
    21. "style-loader": "^0.13.1",
    22. "webpack": "^1.13.0"
    23. }
    24. }
  • Add webpack configuration ‘webpack.config.js’
    ```javascript
    var webpack = require(‘webpack’);
    var ExtractTextPlugin = require(‘extract-text-webpack-plugin’);

var output = ‘./wwwroot’;

module.exports = {
entry: {
‘bundle’: ‘./Scripts/app.js’
},

  1. output: {
  2. path: output,
  3. filename: '[name].js'
  4. },
  5. resolve: {
  6. extensions: ['', '.js', '.json']
  7. },
  8. module: {
  9. loaders: [
  10. { test: /\.js/, loader: 'babel', exclude: /node_modules/ },
  11. { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader') }
  12. ]
  13. },
  14. plugins: [
  15. new ExtractTextPlugin('style.css', { allChunks: true })
  16. ]

};

  1. * Install 'NPM Task Runner' extension
  2. ![npm-task-runner](https://cloud.githubusercontent.com/assets/8171434/22973925/2a6cd7ee-f380-11e6-89b8-b27fe68a6d1b.png)
  3. * Configure 'After Build' step in 'Task Runner Explorer'
  4. ![after-build-task-runner-explorer](https://cloud.githubusercontent.com/assets/8171434/22974769/25727768-f384-11e6-96ca-8670b67b805c.png)
  5. ```json
  6. "-vs-binding": { "AfterBuild": [ "start" ] }
  • Add ‘Get’ action to GraphQL controller and GraphiQL view (~/Views/GraphQL/index.cshtml)

    1. // ...
    2. public class GraphQLController : Controller
    3. {
    4. // ...
    5. [HttpGet]
    6. public IActionResult Index()
    7. {
    8. return View();
    9. }
    10. // ...
    11. }
    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="utf-8" />
    5. <meta name="viewport" content="width=device-width" />
    6. <title>GraphiQL</title>
    7. <link rel="stylesheet" href="~/style.css" />
    8. </head>
    9. <body>
    10. <div id="app"></div>
    11. <script src="~/bundle.js" type="text/javascript"></script>
    12. </body>
    13. </html>
  • Add GraphiQL scripts and styles (app.js and app.css to ~/GraphiQL)

    • app.js

      1. import React from 'react';
      2. import ReactDOM from 'react-dom';
      3. import GraphiQL from 'graphiql';
      4. import fetch from 'isomorphic-fetch';
      5. import 'graphiql/graphiql.css';
      6. import './app.css';
      7. function graphQLFetcher(graphQLParams) {
      8. return fetch(window.location.origin + '/graphql', {
      9. method: 'post',
      10. headers: { 'Content-Type': 'application/json' },
      11. body: JSON.stringify(graphQLParams)
      12. }).then(response => response.json());
      13. }
      14. ReactDOM.render(<GraphiQL fetcher={graphQLFetcher}></GraphiQL>, document.getElementById('app'));
    • app.css

      1. html, body {
      2. height: 100%;
      3. margin: 0;
      4. overflow: hidden;
      5. width: 100%;
      6. }
      7. #app {
      8. height: 100vh;
      9. }
  • Add static files support

    • Add ‘Microsoft.AspNetCore.StaticFiles’ NuGet
      static-files-nuget

    • Update configuration in ‘Startup.cs’

      1. // ...
      2. public void Configure(IApplicationBuilder app, IHostingEnvironment env,
      3. ILoggerFactory loggerFactory, StarWarsContext db)
      4. {
      5. loggerFactory.AddConsole(Configuration.GetSection("Logging"));
      6. loggerFactory.AddDebug();
      7. app.UseStaticFiles();
      8. app.UseMvc();
      9. db.EnsureSeedData();
      10. }
      11. // ...
  • Build project and check if bundles were created by webpack under ~/wwwroot
    bundles-created-by-webpack

  • Run project and enjoy GraphiQL
    graphiql

Unit Tests

  • Create ‘Class Library (.NET Core)’ type ‘StarWars.Tests.Unit’ project
    unit-tests-project

  • Install ‘xunit’ NuGet package in StarWars.Tests.Unit project
    xunit-nuget

  • Install ‘dotnet-test-xunit’ NuGet package in StarWars.Tests.Unit project
    dotnet-test-xunit-nuget

  • Make changes to project.json

    • Set ‘testRunner’
    • Reference ‘StarWars.Data’ project
    • Set ‘runtimes’
      ```json
      {
      “version”: “1.0.0-*”,
      “testRunner”: “xunit”,
      “dependencies”: {
      “dotnet-test-xunit”: “2.2.0-preview2-build1029”,
      “Microsoft.NETCore.App”: “1.1.0”,
      “xunit”: “2.1.0”,
      “StarWars.Data”: {
      “target”: “project”
      }
      },

    “frameworks”: {
    “netcoreapp1.1”: {

    1. "imports": [
    2. "dotnet5.6",
    3. "portable-net45+win8"
    4. ]

    }
    },

    “runtimes”: {
    “win10-x64”: {}
    }
    }
    ```

  • Create first test for in memory droid repository
    ```csharp
    using StarWars.Data.InMemory;
    using Xunit;

namespace StarWars.Tests.Unit.Data.InMemory
{
public class DroidRepositoryShould
{
private readonly DroidRepository _droidRepository;
public DroidRepositoryShould()
{
// Given
_droidRepository = new DroidRepository();
}

  1. [Fact]
  2. public async void ReturnR2D2DroidGivenIdOf1()
  3. {
  4. // When
  5. var droid = await _droidRepository.Get(1);
  6. // Then
  7. Assert.NotNull(droid);
  8. Assert.Equal("WRONG_NAME", droid.Name);
  9. }
  10. }

}

  1. * Build and make sure that test is discovered by 'Test Explorer'
  2. ![test-explorer-first-unit-test](https://cloud.githubusercontent.com/assets/8171434/23020737/c1b268ee-f448-11e6-96de-80928174a335.png)
  3. * Run test - it should fail (we want to make sure that we are testing the right thing)
  4. ![first-unit-test-fail](https://cloud.githubusercontent.com/assets/8171434/23022083/56733e6c-f44f-11e6-8f64-84e9da06d879.png)
  5. * Fix test
  6. ```csharp
  7. // ...
  8. [Fact]
  9. public async void ReturnR2D2DroidGivenIdOf1()
  10. {
  11. // When
  12. var droid = await _droidRepository.Get(1);
  13. // Then
  14. Assert.NotNull(droid);
  15. Assert.Equal("R2-D2", droid.Name);
  16. }
  17. // ...
  • Run test again - it should pass
    first-unit-test-pass

  • Install ‘Moq’ NuGet package
    moq-nuget

  • Install ‘Microsoft.EntityFrameworkCore.InMemory’ NuGet package
    ef-in-memory-nuget

  • Add reference to ‘StarWars.Core’ in project.json

    1. {
    2. "dependencies": {
    3. "StarWars.Core": {
    4. "target": "project"
    5. }
    6. }
    7. }
  • Create EF droid repository unit test
    ```csharp
    using Microsoft.EntityFrameworkCore;
    using StarWars.Core.Models;
    using StarWars.Data.EntityFramework;
    using StarWars.Data.EntityFramework.Repositories;
    using Xunit;

namespace StarWars.Tests.Unit.Data.EntityFramework.Repositories
{
public class DroidRepositoryShould
{
private readonly DroidRepository _droidRepository;
public DroidRepositoryShould()
{
// Given
// https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/in-memory
var options = new DbContextOptionsBuilder()
.UseInMemoryDatabase(databaseName: “StarWars”)
.Options;
using (var context = new StarWarsContext(options))
{
context.Droids.Add(new Droid { Id = 1, Name = “R2-D2” });
context.SaveChanges();
}
var starWarsContext = new StarWarsContext(options);
_droidRepository = new DroidRepository(starWarsContext);
}

  1. [Fact]
  2. public async void ReturnR2D2DroidGivenIdOf1()
  3. {
  4. // When
  5. var droid = await _droidRepository.Get(1);
  6. // Then
  7. Assert.NotNull(droid);
  8. Assert.Equal("R2-D2", droid.Name);
  9. }
  10. }

}

  1. * Create GraphQLController unit test
  2. * First refactor controller to be more testable by using constructor injection
  3. ```csharp
  4. using GraphQL;
  5. using GraphQL.Types;
  6. using Microsoft.AspNetCore.Mvc;
  7. using StarWars.Api.Models;
  8. using System.Threading.Tasks;
  9. namespace StarWars.Api.Controllers
  10. {
  11. [Route("graphql")]
  12. public class GraphQLController : Controller
  13. {
  14. private IDocumentExecuter _documentExecuter { get; set; }
  15. private ISchema _schema { get; set; }
  16. public GraphQLController(IDocumentExecuter documentExecuter, ISchema schema)
  17. {
  18. _documentExecuter = documentExecuter;
  19. _schema = schema;
  20. }
  21. [HttpGet]
  22. public IActionResult Index()
  23. {
  24. return View();
  25. }
  26. [HttpPost]
  27. public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
  28. {
  29. var executionOptions = new ExecutionOptions { Schema = _schema, Query = query.Query };
  30. var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);
  31. if (result.Errors?.Count > 0)
  32. {
  33. return BadRequest(result.Errors);
  34. }
  35. return Ok(result);
  36. }
  37. }
  38. }
  1. * Configure dependency injection in 'Startup.cs'
  2. ```csharp
  3. // ...
  4. public void ConfigureServices(IServiceCollection services)
  5. {
  6. // ...
  7. services.AddTransient<IDocumentExecuter, DocumentExecuter>();
  8. var sp = services.BuildServiceProvider();
  9. services.AddTransient<ISchema>(_ => new Schema { Query = sp.GetService<StarWarsQuery>() });
  10. }
  11. // ...
  12. ```
  13. * Create test for 'Index' and 'Post' actions
  14. ```csharp
  15. using GraphQL;
  16. using GraphQL.Types;
  17. using Microsoft.AspNetCore.Mvc;
  18. using Moq;
  19. using StarWars.Api.Controllers;
  20. using StarWars.Api.Models;
  21. using System.Threading.Tasks;
  22. using Xunit;
  23. namespace StarWars.Tests.Unit.Api.Controllers
  24. {
  25. public class GraphQLControllerShould
  26. {
  27. private GraphQLController _graphqlController { get; set; }
  28. public GraphQLControllerShould()
  29. {
  30. // Given
  31. var documentExecutor = new Mock<IDocumentExecuter>();
  32. documentExecutor.Setup(x => x.ExecuteAsync(It.IsAny<ExecutionOptions>())).Returns(Task.FromResult(new ExecutionResult()));
  33. var schema = new Mock<ISchema>();
  34. _graphqlController = new GraphQLController(documentExecutor.Object, schema.Object);
  35. }
  36. [Fact]
  37. public void ReturnNotNullViewResult()
  38. {
  39. // When
  40. var result = _graphqlController.Index() as ViewResult;
  41. // Then
  42. Assert.NotNull(result);
  43. Assert.IsType<ViewResult>(result);
  44. }
  45. [Fact]
  46. public async void ReturnNotNullExecutionResult()
  47. {
  48. // Given
  49. var query = new GraphQLQuery { Query = @"{ ""query"": ""query { hero { id name } }""" };
  50. // When
  51. var result = await _graphqlController.Post(query);
  52. // Then
  53. Assert.NotNull(result);
  54. var okObjectResult = Assert.IsType<OkObjectResult>(result);
  55. var executionResult = okObjectResult.Value;
  56. Assert.NotNull(executionResult);
  57. }
  58. }
  59. }
  60. ```

Visual Studio 2017 RC upgrade

  • Open solution in VS 2017 and let the upgrade tool do the job
    vs-2017-rc-upgrade

  • Upgrade of ‘StarWars.Tests.Unit’ failed, so I had to remove all project dependencies and reload it

    1. {
    2. "dependencies": {
    3. // remove this:
    4. "StarWars.Data": {
    5. "target": "project"
    6. },
    7. "StarWars.Core": {
    8. "target": "project"
    9. }
    10. // ...
    11. }
    12. }
  • Replace old test txplorer runner for the xUnit.net framework (dotnet-test-xunit) with new one (xunit.runner.visualstudio)
    xunit-runner-visualstudio-nuget

  • Install (xunit.runner.visualstudio) dependency (Microsoft.DotNet.InternalAbstractions)
    microsoft-dotnet-internal-abstractions-nuget

Integration Tests

  • Create ‘xUnit Test Project (.NET Core)’ type ‘StarWars.Tests.Integration’ project
    integration-tests-project

  • Change target framework from ‘netcoreapp1.0’ to ‘netcoreapp1.1’

    1. <Project Sdk="Microsoft.NET.Sdk">
    2. <PropertyGroup>
    3. <TargetFramework>netcoreapp1.1</TargetFramework>
    4. </PropertyGroup>
    5. <!--...-->
    6. </Project>
  • Install ‘Microsoft.AspNetCore.TestHost’ NuGet package
    test-host-nuget

  • Use EF in memory database for ‘Test’ evironment

    • Install ‘Microsoft.EntityFrameworkCore.InMemory’ NuGet package
      ef-in-memory-nuget-api-project

    • Configure it in ‘Startup.cs’

      1. // ...
      2. private IHostingEnvironment Env { get; set; }
      3. public class Startup
      4. {
      5. // ...
      6. Env = env;
      7. }
      8. public void ConfigureServices(IServiceCollection services)
      9. {
      10. // ...
      11. if (Env.IsEnvironment("Test"))
      12. {
      13. services.AddDbContext<StarWarsContext>(options =>
      14. options.UseInMemoryDatabase(databaseName: "StarWars"));
      15. }
      16. else
      17. {
      18. services.AddDbContext<StarWarsContext>(options =>
      19. options.UseSqlServer(Configuration["ConnectionStrings:StarWarsDatabaseConnection"]));
      20. }
      21. // ...
      22. }
      23. // ...
  • Create integration test for GraphQL query (POST)
    ```csharp
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using StarWars.Api;
    using System.Net.Http;
    using System.Text;
    using Xunit;

namespace StarWars.Tests.Integration.Api.Controllers
{
public class GraphQLControllerShould
{
private readonly TestServer _server;
private readonly HttpClient _client;

  1. public GraphQLControllerShould()
  2. {
  3. _server = new TestServer(new WebHostBuilder()
  4. .UseEnvironment("Test")
  5. .UseStartup<Startup>()
  6. );
  7. _client = _server.CreateClient();
  8. }
  9. [Fact]
  10. public async void ReturnR2D2Droid()
  11. {
  12. // Given
  13. var query = @"{
  14. ""query"": ""query { hero { id name } }""
  15. }";
  16. var content = new StringContent(query, Encoding.UTF8, "application/json");
  17. // When
  18. var response = await _client.PostAsync("/graphql", content);
  19. // Then
  20. response.EnsureSuccessStatusCode();
  21. var responseString = await response.Content.ReadAsStringAsync();
  22. Assert.Contains("R2-D2", responseString);
  23. }
  24. }

}

  1. #### Logs
  2. * Make sure that logger is configured in Startup.cs
  3. ```csharp
  4. public void Configure(IApplicationBuilder app, IHostingEnvironment env,
  5. ILoggerFactory loggerFactory, StarWarsContext db)
  6. {
  7. loggerFactory.AddConsole(Configuration.GetSection("Logging"));
  8. loggerFactory.AddDebug();
  9. // ...
  10. }
  • Override ToString method of GraphQLQuery class

    1. public override string ToString()
    2. {
    3. var builder = new StringBuilder();
    4. builder.AppendLine();
    5. if (!string.IsNullOrWhiteSpace(OperationName))
    6. {
    7. builder.AppendLine($"OperationName = {OperationName}");
    8. }
    9. if (!string.IsNullOrWhiteSpace(NamedQuery))
    10. {
    11. builder.AppendLine($"NamedQuery = {NamedQuery}");
    12. }
    13. if (!string.IsNullOrWhiteSpace(Query))
    14. {
    15. builder.AppendLine($"Query = {Query}");
    16. }
    17. if (!string.IsNullOrWhiteSpace(Variables))
    18. {
    19. builder.AppendLine($"Variables = {Variables}");
    20. }
    21. return builder.ToString();
    22. }
  • Add logger to GraphQLController

    1. public class GraphQLController : Controller
    2. {
    3. // ...
    4. private readonly ILogger _logger;
    5. public GraphQLController(IDocumentExecuter documentExecuter, ISchema schema, ILogger<GraphQLController> logger)
    6. {
    7. // ...
    8. _logger = logger;
    9. }
    10. [HttpGet]
    11. public IActionResult Index()
    12. {
    13. _logger.LogInformation("Got request for GraphiQL. Sending GUI back");
    14. return View();
    15. }
    16. [HttpPost]
    17. public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
    18. {
    19. // ...
    20. if (result.Errors?.Count > 0)
    21. {
    22. _logger.LogError("GraphQL errors: {0}", result.Errors);
    23. return BadRequest(result);
    24. }
    25. _logger.LogDebug("GraphQL execution result: {result}", JsonConvert.SerializeObject(result.Data));
    26. return Ok(result);
    27. }
    28. }
  • Add logger to DroidRepository

    1. namespace StarWars.Data.EntityFramework.Repositories
    2. {
    3. public class DroidRepository : IDroidRepository
    4. {
    5. private StarWarsContext _db { get; set; }
    6. private readonly ILogger _logger;
    7. public DroidRepository(StarWarsContext db, ILogger<DroidRepository> logger)
    8. {
    9. _db = db;
    10. _logger = logger;
    11. }
    12. public Task<Droid> Get(int id)
    13. {
    14. _logger.LogInformation("Get droid with id = {id}", id);
    15. return _db.Droids.FirstOrDefaultAsync(droid => droid.Id == id);
    16. }
    17. }
    18. }
  • Add logger to StarWarsContext

    1. namespace StarWars.Data.EntityFramework
    2. {
    3. public class StarWarsContext : DbContext
    4. {
    5. public readonly ILogger _logger;
    6. public StarWarsContext(DbContextOptions options, ILogger<StarWarsContext> logger)
    7. : base(options)
    8. {
    9. _logger = logger;
    10. // ...
    11. }
    12. }
    13. }
  • Add logger to StarWarsSeedData

    1. namespace StarWars.Data.EntityFramework.Seed
    2. {
    3. public static class StarWarsSeedData
    4. {
    5. public static void EnsureSeedData(this StarWarsContext db)
    6. {
    7. db._logger.LogInformation("Seeding database");
    8. if (!db.Droids.Any())
    9. {
    10. db._logger.LogInformation("Seeding droids");
    11. // ...
    12. }
    13. }
    14. }
    15. }
  • Fix controller unit test

    1. public class GraphQLControllerShould
    2. {
    3. public GraphQLControllerShould()
    4. {
    5. // ...
    6. var logger = new Mock<ILogger<GraphQLController>>();
    7. _graphqlController = new GraphQLController(documentExecutor.Object, schema.Object, logger.Object);
    8. }
    9. // ...
    10. }
  • Fix repository unit test
    ```csharp
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using Moq;
    using StarWars.Core.Models;
    using StarWars.Data.EntityFramework;
    using StarWars.Data.EntityFramework.Repositories;
    using Xunit;

namespace StarWars.Tests.Unit.Data.EntityFramework.Repositories
{
public class DroidRepositoryShould
{
public DroidRepositoryShould()
{
var dbLogger = new Mock>();
// …
using (var context = new StarWarsContext(options, dbLogger.Object))
{
// …
}
// …
var repoLogger = new Mock>();
_droidRepository = new DroidRepository(starWarsContext, repoLogger.Object);
}
}
}

  1. * Enjoy console logs
  2. ![console-logs-1](https://cloud.githubusercontent.com/assets/8171434/23155559/6b17afe8-f813-11e6-88e0-2923270f4ee8.png)
  3. ![console-logs-2](https://cloud.githubusercontent.com/assets/8171434/23155802/68f8e604-f814-11e6-90a7-44d09e851a51.png)
  4. #### Code Coverage
  5. * Install OpenCover NuGet package
  6. ![open-cover-nuget](https://cloud.githubusercontent.com/assets/8171434/23206746/247aa694-f8ef-11e6-9b14-d0ade0453b94.png)
  7. * Add path to OpenCover tools to 'Path' environment variable. In my case it was:

C:\Users\Jacek_Kosciesza.nuget\packages\opencover\4.6.519\tools\

  1. * Set 'Full' debug type in all projects (StarWars.Api.csproj, StarWars.Core.csproj, StarWars.Data.csproj). This is needed to produce *.pdb files which are understandable by OpenCover.
  2. ```xml
  3. <Project Sdk="Microsoft.NET.Sdk.Web">
  4. <PropertyGroup>
  5. <!--...-->
  6. <DebugType>Full</DebugType>
  7. </PropertyGroup>
  8. <!--...-->
  9. </Project>
  • Run OpenCover in the console

    1. OpenCover.Console.exe
    2. -target:"dotnet.exe"
    3. -targetargs:"test -f netcoreapp1.1 -c Release Tests/StarWars.Tests.Unit/StarWars.Tests.Unit.csproj"
    4. -hideskipped:File
    5. -output:coverage/unit/coverage.xml
    6. -oldStyle
    7. -filter:"+[StarWars*]* -[StarWars.Tests*]* -[StarWars.Api]*Program -[StarWars.Api]*Startup -[StarWars.Data]*EntityFramework.Workaround.Program -[StarWars.Data]*EntityFramework.Migrations* -[StarWars.Data]*EntityFramework.Seed*"
    8. -searchdirs:"Tests/StarWars.Tests.Unit/bin/Release/netcoreapp1.1"
    9. -register:user

    open-cover-unit-tests-results

  • Install ‘ReportGenerator’ NuGet package
    report-generator-nuget

  • Create simple script (unit-tests.bat)

    1. mkdir coverage\unit
    2. OpenCover.Console.exe -target:"dotnet.exe" -targetargs:"test -f netcoreapp1.1 -c Release Tests/StarWars.Tests.Unit/StarWars.Tests.Unit.csproj" -hideskipped:File -output:coverage/unit/coverage.xml -oldStyle -filter:"+[StarWars*]* -[StarWars.Tests*]* -[StarWars.Api]*Program -[StarWars.Api]*Startup -[StarWars.Data]*EntityFramework.Workaround.Program -[StarWars.Data]*EntityFramework.Migrations* -[StarWars.Data]*EntityFramework.Seed*" -searchdirs:"Tests/StarWars.Tests.Unit/bin/Release/netcoreapp1.1" -register:user
    3. ReportGenerator.exe -reports:coverage/unit/coverage.xml -targetdir:coverage/unit -verbosity:Error
    4. start .\coverage\unit\index.htm
  • Enjoy HTML based code coverage report
    open-cover-html-report-results

    Continous Integration

  • Create new project in VSTS (Visual Studio Team Services)
    vsts-new-project

  • Create new build definition “ASP.NET Core Preview”. Select GitHub, Hosted VS2017 default agent queue and continous integration. At the moment hosted agents don’t support *.csproj based .NET Core projects, so we have to wait for a while, see this issue: Support for .NET Core .csproj files? #3311
    vsts-new-build-definition
    vsts-new-build-definition-github-hosted-vs2017

  • Add new GitHub service connection
    vsts-new-github-service-connection

  • Setup repository
    vsts-setup-repository

  • Switch to “New Build Editor”
    vsts-new-build-editor

  • Setup build process (tasks, build steps)
    vsts-setup-build-steps

  • Setup projects in Test build step

    1. **/Tests/StarWars.Tests.Unit/StarWars.Tests.Unit.csproj;**/Tests/StarWars.Tests.Integration/StarWars.Tests.Integration.csproj

    vsts-setup-projects-test-build-setp

    • Queue build. Make sure it succeeded and executed unit and integration tests.
      vsts-queue-build
      vsts-build-succeeded
  • Enable build badge (after save you will see link to build status image).
    vsts-enable-badge

Advanced

Full ‘Star Wars’ database (see Facebook GraphQL and GraphQL.js)

  • Create models

    1. namespace StarWars.Core.Models
    2. {
    3. public class Episode
    4. {
    5. public int Id { get; set; }
    6. public string Title { get; set; }
    7. public virtual ICollection<CharacterEpisode> CharacterEpisodes { get; set; }
    8. }
    9. }
    1. namespace StarWars.Core.Models
    2. {
    3. public class Planet
    4. {
    5. public int Id { get; set; }
    6. public string Name { get; set; }
    7. public ICollection<Human> Humans { get; set; }
    8. }
    9. }
    1. namespace StarWars.Core.Models
    2. {
    3. public class Character
    4. {
    5. public int Id { get; set; }
    6. public string Name { get; set; }
    7. public virtual ICollection<CharacterEpisode> CharacterEpisodes { get; set; }
    8. public virtual ICollection<CharacterFriend> CharacterFriends { get; set; }
    9. public virtual ICollection<CharacterFriend> FriendCharacters { get; set; }
    10. }
    11. }
    1. namespace StarWars.Core.Models
    2. {
    3. public class CharacterEpisode
    4. {
    5. public int CharacterId { get; set; }
    6. public Character Character { get; set; }
    7. public int EpisodeId { get; set; }
    8. public Episode Episode { get; set; }
    9. }
    10. }
    1. namespace StarWars.Core.Models
    2. {
    3. public class CharacterFriend
    4. {
    5. public int CharacterId { get; set; }
    6. public Character Character { get; set; }
    7. public int FriendId { get; set; }
    8. public Character Friend { get; set; }
    9. }
    10. }
    1. namespace StarWars.Core.Models
    2. {
    3. public class Droid : Character
    4. {
    5. public string PrimaryFunction { get; set; }
    6. }
    7. }
    1. namespace StarWars.Core.Models
    2. {
    3. public class Human : Character
    4. {
    5. public Planet HomePlanet { get; set; }
    6. }
    7. }
  • Update StarWarsContext

    1. namespace StarWars.Data.EntityFramework
    2. {
    3. public class StarWarsContext : DbContext
    4. {
    5. // ...
    6. protected override void OnModelCreating(ModelBuilder modelBuilder)
    7. {
    8. // https://docs.microsoft.com/en-us/ef/core/modeling/relationships
    9. // http://stackoverflow.com/questions/38520695/multiple-relationships-to-the-same-table-in-ef7core
    10. // episodes
    11. modelBuilder.Entity<Episode>().HasKey(c => c.Id);
    12. modelBuilder.Entity<Episode>().Property(e => e.Id).ValueGeneratedNever();
    13. // planets
    14. modelBuilder.Entity<Planet>().HasKey(c => c.Id);
    15. modelBuilder.Entity<Planet>().Property(e => e.Id).ValueGeneratedNever();
    16. // characters
    17. modelBuilder.Entity<Character>().HasKey(c => c.Id);
    18. modelBuilder.Entity<Character>().Property(e => e.Id).ValueGeneratedNever();
    19. // characters-friends
    20. modelBuilder.Entity<CharacterFriend>().HasKey(t => new { t.CharacterId, t.FriendId});
    21. modelBuilder.Entity<CharacterFriend>()
    22. .HasOne(cf => cf.Character)
    23. .WithMany(c => c.CharacterFriends)
    24. .HasForeignKey(cf => cf.CharacterId)
    25. .OnDelete(DeleteBehavior.Restrict);
    26. modelBuilder.Entity<CharacterFriend>()
    27. .HasOne(cf => cf.Friend)
    28. .WithMany(t => t.FriendCharacters)
    29. .HasForeignKey(cf => cf.FriendId)
    30. .OnDelete(DeleteBehavior.Restrict);
    31. // characters-episodes
    32. modelBuilder.Entity<CharacterEpisode>().HasKey(t => new { t.CharacterId, t.EpisodeId });
    33. modelBuilder.Entity<CharacterEpisode>()
    34. .HasOne(cf => cf.Character)
    35. .WithMany(c => c.CharacterEpisodes)
    36. .HasForeignKey(cf => cf.CharacterId)
    37. .OnDelete(DeleteBehavior.Restrict);
    38. modelBuilder.Entity<CharacterEpisode>()
    39. .HasOne(cf => cf.Episode)
    40. .WithMany(t => t.CharacterEpisodes)
    41. .HasForeignKey(cf => cf.EpisodeId)
    42. .OnDelete(DeleteBehavior.Restrict);
    43. // humans
    44. modelBuilder.Entity<Human>().HasOne(h => h.HomePlanet).WithMany(p => p.Humans);
    45. }
    46. public virtual DbSet<Episode> Episodes { get; set; }
    47. public virtual DbSet<Planet> Planets { get; set; }
    48. public virtual DbSet<Character> Characters { get; set; }
    49. public virtual DbSet<CharacterFriend> CharacterFriends { get; set; }
    50. public virtual DbSet<CharacterEpisode> CharacterEpisodes { get; set; }
    51. public virtual DbSet<Droid> Droids { get; set; }
    52. public virtual DbSet<Human> Humans { get; set; }
    53. }
    54. }
  • Update database seed data

    1. namespace StarWars.Data.EntityFramework.Seed
    2. {
    3. public static class StarWarsSeedData
    4. {
    5. public static void EnsureSeedData(this StarWarsContext db)
    6. {
    7. db._logger.LogInformation("Seeding database");
    8. // episodes
    9. var newhope = new Episode { Id = 4, Title = "NEWHOPE" };
    10. var empire = new Episode { Id = 5, Title = "EMPIRE" };
    11. var jedi = new Episode { Id = 6, Title = "JEDI" };
    12. var episodes = new List<Episode>
    13. {
    14. newhope,
    15. empire,
    16. jedi,
    17. };
    18. if (!db.Episodes.Any())
    19. {
    20. db._logger.LogInformation("Seeding episodes");
    21. db.Episodes.AddRange(episodes);
    22. db.SaveChanges();
    23. }
    24. // planets
    25. var tatooine = new Planet { Id = 1, Name = "Tatooine" };
    26. var alderaan = new Planet { Id = 2, Name = "Alderaan" };
    27. var planets = new List<Planet>
    28. {
    29. tatooine,
    30. alderaan
    31. };
    32. if (!db.Planets.Any())
    33. {
    34. db._logger.LogInformation("Seeding planets");
    35. db.Planets.AddRange(planets);
    36. db.SaveChanges();
    37. }
    38. // humans
    39. var luke = new Human
    40. {
    41. Id = 1000,
    42. Name = "Luke Skywalker",
    43. CharacterEpisodes = new List<CharacterEpisode>
    44. {
    45. new CharacterEpisode { Episode = newhope },
    46. new CharacterEpisode { Episode = empire },
    47. new CharacterEpisode { Episode = jedi }
    48. },
    49. HomePlanet = tatooine
    50. };
    51. var vader = new Human
    52. {
    53. Id = 1001,
    54. Name = "Darth Vader",
    55. CharacterEpisodes = new List<CharacterEpisode>
    56. {
    57. new CharacterEpisode { Episode = newhope },
    58. new CharacterEpisode { Episode = empire },
    59. new CharacterEpisode { Episode = jedi }
    60. },
    61. HomePlanet = tatooine
    62. };
    63. var han = new Human
    64. {
    65. Id = 1002,
    66. Name = "Han Solo",
    67. CharacterEpisodes = new List<CharacterEpisode>
    68. {
    69. new CharacterEpisode { Episode = newhope },
    70. new CharacterEpisode { Episode = empire },
    71. new CharacterEpisode { Episode = jedi }
    72. },
    73. HomePlanet = tatooine
    74. };
    75. var leia = new Human
    76. {
    77. Id = 1003,
    78. Name = "Leia Organa",
    79. CharacterEpisodes = new List<CharacterEpisode>
    80. {
    81. new CharacterEpisode { Episode = newhope },
    82. new CharacterEpisode { Episode = empire },
    83. new CharacterEpisode { Episode = jedi }
    84. },
    85. HomePlanet = alderaan
    86. };
    87. var tarkin = new Human
    88. {
    89. Id = 1004,
    90. Name = "Wilhuff Tarkin",
    91. CharacterEpisodes = new List<CharacterEpisode>
    92. {
    93. new CharacterEpisode { Episode = newhope }
    94. },
    95. };
    96. var humans = new List<Human>
    97. {
    98. luke,
    99. vader,
    100. han,
    101. leia,
    102. tarkin
    103. };
    104. if (!db.Humans.Any())
    105. {
    106. db._logger.LogInformation("Seeding humans");
    107. db.Humans.AddRange(humans);
    108. db.SaveChanges();
    109. }
    110. // droids
    111. var threepio = new Droid
    112. {
    113. Id = 2000,
    114. Name = "C-3PO",
    115. CharacterEpisodes = new List<CharacterEpisode>
    116. {
    117. new CharacterEpisode { Episode = newhope },
    118. new CharacterEpisode { Episode = empire },
    119. new CharacterEpisode { Episode = jedi }
    120. },
    121. PrimaryFunction = "Protocol"
    122. };
    123. var artoo = new Droid
    124. {
    125. Id = 2001,
    126. Name = "R2-D2",
    127. CharacterEpisodes = new List<CharacterEpisode>
    128. {
    129. new CharacterEpisode { Episode = newhope },
    130. new CharacterEpisode { Episode = empire },
    131. new CharacterEpisode { Episode = jedi }
    132. },
    133. PrimaryFunction = "Astromech"
    134. };
    135. var droids = new List<Droid>
    136. {
    137. threepio,
    138. artoo
    139. };
    140. if (!db.Droids.Any())
    141. {
    142. db._logger.LogInformation("Seeding droids");
    143. db.Droids.AddRange(droids);
    144. db.SaveChanges();
    145. }
    146. // update character's friends
    147. luke.CharacterFriends = new List<CharacterFriend>
    148. {
    149. new CharacterFriend { Friend = han },
    150. new CharacterFriend { Friend = leia },
    151. new CharacterFriend { Friend = threepio },
    152. new CharacterFriend { Friend = artoo }
    153. };
    154. vader.CharacterFriends = new List<CharacterFriend>
    155. {
    156. new CharacterFriend { Friend = tarkin }
    157. };
    158. han.CharacterFriends = new List<CharacterFriend>
    159. {
    160. new CharacterFriend { Friend = luke },
    161. new CharacterFriend { Friend = leia },
    162. new CharacterFriend { Friend = artoo }
    163. };
    164. leia.CharacterFriends = new List<CharacterFriend>
    165. {
    166. new CharacterFriend { Friend = luke },
    167. new CharacterFriend { Friend = han },
    168. new CharacterFriend { Friend = threepio },
    169. new CharacterFriend { Friend = artoo }
    170. };
    171. tarkin.CharacterFriends = new List<CharacterFriend>
    172. {
    173. new CharacterFriend { Friend = vader }
    174. };
    175. threepio.CharacterFriends = new List<CharacterFriend>
    176. {
    177. new CharacterFriend { Friend = luke },
    178. new CharacterFriend { Friend = han },
    179. new CharacterFriend { Friend = leia },
    180. new CharacterFriend { Friend = artoo }
    181. };
    182. artoo.CharacterFriends = new List<CharacterFriend>
    183. {
    184. new CharacterFriend { Friend = luke },
    185. new CharacterFriend { Friend = han },
    186. new CharacterFriend { Friend = leia }
    187. };
    188. var characters = new List<Character>
    189. {
    190. luke,
    191. vader,
    192. han,
    193. leia,
    194. tarkin,
    195. threepio,
    196. artoo
    197. };
    198. if (!db.CharacterFriends.Any())
    199. {
    200. db._logger.LogInformation("Seeding character's friends");
    201. db.Characters.UpdateRange(characters);
    202. db.SaveChanges();
    203. }
    204. }
    205. }
    206. }
  • Add ‘Microsoft.EntityFrameworkCore.Tools’ NuGet
    ef-tools-nuget

  • Set ‘StarWars.Data’ as a StartUp project

  • Add ‘Full’ migrations
    ef-full-migration

  • Update database
    ef-update-database-full
    ef-update-database-full-ssms

  • Set ‘StarWars.Api’ as a StartUp project

  • Run ‘StarWars.Api’ to seed database
    ef-seed-full-database
    seeded-full-database-smss

  • Create integration test checking EF configuration and seeded data

    1. namespace StarWars.Tests.Integration.Data.EntityFramework
    2. {
    3. public class StarWarsContextShould
    4. {
    5. [Fact]
    6. public async void ReturnR2D2Droid()
    7. {
    8. // Given
    9. using (var db = new StarWarsContext())
    10. {
    11. // When
    12. var r2d2 = await db.Droids
    13. .Include("CharacterEpisodes.Episode")
    14. .Include("CharacterFriends.Friend")
    15. .FirstOrDefaultAsync(d => d.Id == 2001);
    16. // Then
    17. Assert.NotNull(r2d2);
    18. Assert.Equal("R2-D2", r2d2.Name);
    19. Assert.Equal("Astromech", r2d2.PrimaryFunction);
    20. var episodes = r2d2.CharacterEpisodes.Select(e => e.Episode.Title);
    21. Assert.Equal(new string[] { "NEWHOPE", "EMPIRE", "JEDI" }, episodes);
    22. var friends = r2d2.CharacterFriends.Select(e => e.Friend.Name);
    23. Assert.Equal(new string[] { "Luke Skywalker", "Han Solo", "Leia Organa" }, friends);
    24. }
    25. }
    26. }
    27. }
  • Make sure all tests pass
    all-tests-pass-full-database

  • Update StarWarsQuery with new hero (“R2-D2”) ID (2001)

    1. namespace StarWars.Api.Models
    2. {
    3. public class StarWarsQuery : ObjectGraphType
    4. {
    5. // ...
    6. public StarWarsQuery(IDroidRepository _droidRepository)
    7. {
    8. Field<DroidType>(
    9. "hero",
    10. resolve: context => _droidRepository.Get(2001)
    11. );
    12. }
    13. }
    14. }
  • Make sure application still works
    graphiql-full-database

Base/generic repository

  • Create generic entity interface

    1. namespace StarWars.Core.Data
    2. {
    3. public interface IEntity<TKey>
    4. {
    5. TKey Id { get; set; }
    6. }
    7. }
  • Update models to inherit from IEntity interface (integer based id)

    1. namespace StarWars.Core.Models
    2. {
    3. public class Character : IEntity<int>
    4. {
    5. // ...
    6. }
    7. }
    1. namespace StarWars.Core.Models
    2. {
    3. public class Episode : IEntity<int>
    4. {
    5. // ...
    6. }
    7. }
    1. namespace StarWars.Core.Models
    2. {
    3. public class Planet : IEntity<int>
    4. {
    5. // ...
    6. }
    7. }
  • Create base/generic repository interface

    1. namespace StarWars.Core.Data
    2. {
    3. public interface IBaseRepository<TEntity, in TKey>
    4. where TEntity : class
    5. {
    6. Task<List<TEntity>> GetAll();
    7. Task<TEntity> Get(TKey id);
    8. TEntity Add(TEntity entity);
    9. void AddRange(IEnumerable<TEntity> entities);
    10. void Delete(TKey id);
    11. void Update(TEntity entity);
    12. Task<bool> SaveChangesAsync();
    13. }
    14. }
  • Create Entity Framework base/generic repository

    1. namespace StarWars.Data.EntityFramework.Repositories
    2. {
    3. public abstract class BaseRepository<TEntity, TKey> : IBaseRepository<TEntity, TKey>
    4. where TEntity : class, IEntity<TKey>, new()
    5. {
    6. protected DbContext _db;
    7. protected readonly ILogger _logger;
    8. protected BaseRepository() { }
    9. protected BaseRepository(DbContext db, ILogger logger)
    10. {
    11. _db = db;
    12. _logger = logger;
    13. }
    14. public virtual Task<List<TEntity>> GetAll()
    15. {
    16. return _db.Set<TEntity>().ToListAsync();
    17. }
    18. public virtual Task<TEntity> Get(TKey id)
    19. {
    20. _logger.LogInformation("Get {type} with id = {id}", typeof(TEntity).Name, id);
    21. return _db.Set<TEntity>().SingleOrDefaultAsync(c => c.Id.Equals(id));
    22. }
    23. public virtual TEntity Add(TEntity entity)
    24. {
    25. _db.Set<TEntity>().Add(entity);
    26. return entity;
    27. }
    28. public void AddRange(IEnumerable<TEntity> entities)
    29. {
    30. _db.Set<TEntity>().AddRange(entities);
    31. }
    32. public virtual void Delete(TKey id)
    33. {
    34. var entity = new TEntity { Id = id };
    35. _db.Set<TEntity>().Attach(entity);
    36. _db.Set<TEntity>().Remove(entity);
    37. }
    38. public virtual async Task<bool> SaveChangesAsync()
    39. {
    40. return (await _db.SaveChangesAsync()) > 0;
    41. }
    42. public virtual void Update(TEntity entity)
    43. {
    44. _db.Set<TEntity>().Attach(entity);
    45. _db.Entry(entity).State = EntityState.Modified;
    46. }
    47. }
    48. }
  • Refactor EF Droid repository

    1. namespace StarWars.Core.Data
    2. {
    3. public interface IDroidRepository : IBaseRepository<Droid, int> { }
    4. }
    1. namespace StarWars.Data.EntityFramework.Repositories
    2. {
    3. public class DroidRepository : BaseRepository<Droid, int>, IDroidRepository
    4. {
    5. public DroidRepository() { }
    6. public DroidRepository(StarWarsContext db, ILogger<DroidRepository> logger)
    7. : base(db, logger)
    8. {
    9. }
    10. }
    11. }
  • Refactor in-memeory Droid repository

    1. namespace StarWars.Data.InMemory
    2. {
    3. public class DroidRepository : IDroidRepository
    4. {
    5. private readonly ILogger _logger;
    6. public DroidRepository() { }
    7. public DroidRepository(ILogger<DroidRepository> logger)
    8. {
    9. _logger = logger;
    10. }
    11. private List<Droid> _droids = new List<Droid> {
    12. new Droid { Id = 1, Name = "R2-D2" }
    13. };
    14. public Task<Droid> Get(int id)
    15. {
    16. _logger.LogInformation("Get droid with id = {id}", id);
    17. return Task.FromResult(_droids.FirstOrDefault(droid => droid.Id == id));
    18. }
    19. // ...
    20. // rest of the methods are not implemented
    21. // for now they are just throwing NotImplementedException
    22. }
    23. }
  • Make sure tests and api stil works

Visual Studio 2017 RTM upgrade

  • Update all NuGet packages for the solution (especially .NET Core v1.1.1)
    vs-2017-rtm-nugets-update

  • Use ‘Package Manger Console’ to fix problems with upgrading ‘Microsoft.NETCore.App’ from v1.1.0 to v.1.1.1 (for some reason Consolidate option does not work). Do upgrade for all projects.
    consolidate-netcore-app

    1. Install-Package Microsoft.NETCore.App

    package-manager-console-netcore-app-upgrade

  • Fix ‘DroidType’ unit test (capitalization of field names)

    1. namespace StarWars.Tests.Unit.Api.Models
    2. {
    3. public class DroidTypeShould
    4. {
    5. [Fact]
    6. public void HaveIdAndNameFields()
    7. {
    8. // When
    9. var droidType = new DroidType();
    10. // Then
    11. Assert.NotNull(droidType);
    12. Assert.True(droidType.HasField("Id"));
    13. Assert.True(droidType.HasField("Name"));
    14. }
    15. }
    16. }

Repositories

  • Create rest of the repositories (Character, Episode, Human, Planet)

    1. namespace StarWars.Core.Data
    2. {
    3. public interface IHumanRepository : IBaseRepository<Human, int> { }
    4. }
    1. namespace StarWars.Data.EntityFramework.Repositories
    2. {
    3. public class HumanRepository : BaseRepository<Human, int>, IHumanRepository
    4. {
    5. public HumanRepository() { }
    6. public HumanRepository(StarWarsContext db, ILogger<HumanRepository> logger)
    7. : base(db, logger)
    8. {
    9. }
    10. }
    11. }
  • Update base repository with ‘include’ versions

    1. namespace StarWars.Core.Data
    2. {
    3. public interface IBaseRepository<TEntity, in TKey>
    4. where TEntity : class
    5. {
    6. // ...
    7. Task<List<TEntity>> GetAll(string include);
    8. Task<List<TEntity>> GetAll(IEnumerable<string> includes);
    9. // ...
    10. Task<TEntity> Get(TKey id, string include);
    11. Task<TEntity> Get(TKey id, IEnumerable<string> includes);
    12. // ...
    13. }
    14. }
    1. namespace StarWars.Data.EntityFramework.Repositories
    2. {
    3. public abstract class BaseRepository<TEntity, TKey> : IBaseRepository<TEntity, TKey>
    4. where TEntity : class, IEntity<TKey>, new()
    5. {
    6. // ...
    7. public Task<List<TEntity>> GetAll(string include)
    8. {
    9. _logger.LogInformation("Get all {type}s (including {include})", typeof(TEntity).Name, include);
    10. return _db.Set<TEntity>().Include(include).ToListAsync();
    11. }
    12. public Task<List<TEntity>> GetAll(IEnumerable<string> includes)
    13. {
    14. _logger.LogInformation("Get all {type}s (including [{includes}])", typeof(TEntity).Name, string.Join(",", includes));
    15. var query = _db.Set<TEntity>().AsQueryable();
    16. query = includes.Aggregate(query, (current, include) => current.Include(include));
    17. return query.ToListAsync();
    18. }
    19. // ...
    20. public Task<TEntity> Get(TKey id, string include)
    21. {
    22. _logger.LogInformation("Get {type} with id = {id} (including {include})", typeof(TEntity).Name, id, include);
    23. return _db.Set<TEntity>().Include(include).SingleOrDefaultAsync(c => c.Id.Equals(id));
    24. }
    25. public Task<TEntity> Get(TKey id, IEnumerable<string> includes)
    26. {
    27. _logger.LogInformation("Get {type} with id = {id} (including [{include}])", typeof(TEntity).Name, id, string.Join(",", includes));
    28. var query = _db.Set<TEntity>().AsQueryable();
    29. query = includes.Aggregate(query, (current, include) => current.Include(include));
    30. return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
    31. }
    32. // ...
    33. }
    34. }
  • Create repositories CRUD unit tests

    1. namespace StarWars.Tests.Unit.Data.EntityFramework.Repositories
    2. {
    3. public class HumanRepositoryShould
    4. {
    5. private readonly HumanRepository _humanRepository;
    6. private DbContextOptions<StarWarsContext> _options;
    7. private Mock<ILogger<StarWarsContext>> _dbLogger;
    8. public HumanRepositoryShould()
    9. {
    10. // Given
    11. _dbLogger = new Mock<ILogger<StarWarsContext>>();
    12. _options = new DbContextOptionsBuilder<StarWarsContext>()
    13. .UseInMemoryDatabase(databaseName: "StarWars_HumanRepositoryShould")
    14. .Options;
    15. using (var context = new StarWarsContext(_options, _dbLogger.Object))
    16. {
    17. context.EnsureSeedData();
    18. }
    19. var starWarsContext = new StarWarsContext(_options, _dbLogger.Object);
    20. var repoLogger = new Mock<ILogger<HumanRepository>>();
    21. _humanRepository = new HumanRepository(starWarsContext, repoLogger.Object);
    22. }
    23. [Fact]
    24. public async void ReturnLukeGivenIdOf1000()
    25. {
    26. // When
    27. var luke = await _humanRepository.Get(1000);
    28. // Then
    29. Assert.NotNull(luke);
    30. Assert.Equal("Luke Skywalker", luke.Name);
    31. }
    32. [Fact]
    33. public async void ReturnLukeFriendsAndEpisodes()
    34. {
    35. // When
    36. var character = await _humanRepository.Get(1000, includes: new[] { "CharacterEpisodes.Episode", "CharacterFriends.Friend" });
    37. // Then
    38. Assert.NotNull(character);
    39. Assert.NotNull(character.CharacterEpisodes);
    40. var episodes = character.CharacterEpisodes.Select(e => e.Episode.Title);
    41. Assert.Equal(new[] { "NEWHOPE", "EMPIRE", "JEDI" }, episodes);
    42. Assert.NotNull(character.CharacterFriends);
    43. var friends = character.CharacterFriends.Select(e => e.Friend.Name);
    44. Assert.Equal(new[] { "Han Solo", "Leia Organa", "C-3PO", "R2-D2" }, friends);
    45. }
    46. [Fact]
    47. public async void ReturnLukesHomePlanet()
    48. {
    49. // When
    50. var luke = await _humanRepository.Get(1000, include: "HomePlanet");
    51. // Then
    52. Assert.NotNull(luke);
    53. Assert.NotNull(luke.HomePlanet);
    54. Assert.Equal("Tatooine", luke.HomePlanet.Name);
    55. }
    56. [Fact]
    57. public async void AddNewHuman()
    58. {
    59. // Given
    60. var human10101 = new Human { Id = 10101, Name = "Human10101" };
    61. // When
    62. _humanRepository.Add(human10101);
    63. var saved = await _humanRepository.SaveChangesAsync();
    64. // Then
    65. Assert.True(saved);
    66. using (var db = new StarWarsContext(_options, _dbLogger.Object))
    67. {
    68. var human = await db.Humans.FindAsync(10101);
    69. Assert.NotNull(human);
    70. Assert.Equal(10101, human.Id);
    71. Assert.Equal("Human10101", human.Name);
    72. // Cleanup
    73. db.Humans.Remove(human);
    74. await db.SaveChangesAsync();
    75. }
    76. }
    77. [Fact]
    78. public async void UpdateExistingHuman()
    79. {
    80. // Given
    81. var vader = await _humanRepository.Get(1001);
    82. vader.Name = "Human1001";
    83. // When
    84. _humanRepository.Update(vader);
    85. var saved = await _humanRepository.SaveChangesAsync();
    86. // Then
    87. Assert.True(saved);
    88. using (var db = new StarWarsContext(_options, _dbLogger.Object))
    89. {
    90. var human = await db.Humans.FindAsync(1001);
    91. Assert.NotNull(human);
    92. Assert.Equal(1001, human.Id);
    93. Assert.Equal("Human1001", human.Name);
    94. // Cleanup
    95. human.Name = "Darth Vader";
    96. db.Humans.Update(human);
    97. await db.SaveChangesAsync();
    98. }
    99. }
    100. [Fact]
    101. public async void DeleteExistingHuman()
    102. {
    103. // Given
    104. using (var db = new StarWarsContext(_options, _dbLogger.Object))
    105. {
    106. var human10102 = new Human { Id = 10102, Name = "Human10102" };
    107. await db.Humans.AddAsync(human10102);
    108. await db.SaveChangesAsync();
    109. }
    110. // When
    111. _humanRepository.Delete(10102);
    112. var saved = await _humanRepository.SaveChangesAsync();
    113. // Then
    114. Assert.True(saved);
    115. using (var db = new StarWarsContext(_options, _dbLogger.Object))
    116. {
    117. var deletedHuman = await db.Humans.FindAsync(10101);
    118. Assert.Null(deletedHuman);
    119. }
    120. }
    121. }
    122. }
  • Check test results
    test-explorer-repositories-crud-tests
    code-coverage-repositories-crud-tests

GraphQL queries

  • TDD (Test First) integration tests for queries at GraphQL Specification by Facebook

    1. namespace StarWars.Tests.Integration.Api.Controllers
    2. {
    3. public class GraphQLControllerShould
    4. {
    5. // ...
    6. [Fact]
    7. [Trait("test", "integration")]
    8. public async void ExecuteHeroNameQuery()
    9. {
    10. // Given
    11. const string query = @"{
    12. ""query"":
    13. ""query HeroNameQuery {
    14. hero {
    15. name
    16. }
    17. }""
    18. }";
    19. var content = new StringContent(query, Encoding.UTF8, "application/json");
    20. // When
    21. var response = await _client.PostAsync("/graphql", content);
    22. // Then
    23. response.EnsureSuccessStatusCode();
    24. var responseString = await response.Content.ReadAsStringAsync();
    25. Assert.NotNull(responseString);
    26. var jobj = JObject.Parse(responseString);
    27. Assert.NotNull(jobj);
    28. Assert.Equal("R2-D2", (string)jobj["data"]["hero"]["name"]);
    29. }
    30. [Fact]
    31. [Trait("test", "integration")]
    32. public async void ExecuteHeroNameAndFriendsQuery()
    33. {
    34. // Given
    35. const string query = @"{
    36. ""query"":
    37. ""query HeroNameAndFriendsQuery {
    38. hero {
    39. id
    40. name
    41. friends {
    42. id
    43. name
    44. }
    45. }
    46. }""
    47. }";
    48. var content = new StringContent(query, Encoding.UTF8, "application/json");
    49. // When
    50. var response = await _client.PostAsync("/graphql", content);
    51. // Then
    52. response.EnsureSuccessStatusCode();
    53. var responseString = await response.Content.ReadAsStringAsync();
    54. Assert.NotNull(responseString);
    55. var jobj = JObject.Parse(responseString);
    56. Assert.NotNull(jobj);
    57. Assert.Equal(3, ((JArray)jobj["data"]["hero"]["friends"]).Count);
    58. Assert.Equal("Luke Skywalker", (string)jobj["data"]["hero"]["friends"][0]["name"]);
    59. Assert.Equal("Han Solo", (string)jobj["data"]["hero"]["friends"][1]["name"]);
    60. Assert.Equal("Leia Organa", (string)jobj["data"]["hero"]["friends"][2]["name"]);
    61. }
    62. [Fact]
    63. [Trait("test", "integration")]
    64. public async void ExecuteNestedQuery()
    65. {
    66. // Given
    67. const string query = @"{
    68. ""query"":
    69. ""query NestedQuery {
    70. hero {
    71. name
    72. friends {
    73. name
    74. appearsIn
    75. friends {
    76. name
    77. }
    78. }
    79. }
    80. }""
    81. }";
    82. var content = new StringContent(query, Encoding.UTF8, "application/json");
    83. // When
    84. var response = await _client.PostAsync("/graphql", content);
    85. // Then
    86. response.EnsureSuccessStatusCode();
    87. var responseString = await response.Content.ReadAsStringAsync();
    88. Assert.NotNull(responseString);
    89. var jobj = JObject.Parse(responseString);
    90. Assert.NotNull(jobj);
    91. var luke = jobj["data"]["hero"]["friends"][0];
    92. var episodes = ((JArray) luke["appearsIn"]).Select(e => (string)e).ToArray();
    93. Assert.Equal(new[] { "NEWHOPE", "EMPIRE", "JEDI" }, episodes);
    94. Assert.Equal(4, ((JArray)luke["friends"]).Count);
    95. Assert.Equal("Han Solo", (string)luke["friends"][0]["name"]);
    96. Assert.Equal("Leia Organa", (string)luke["friends"][1]["name"]);
    97. Assert.Equal("C-3PO", (string)luke["friends"][2]["name"]);
    98. Assert.Equal("R2-D2", (string)luke["friends"][3]["name"]);
    99. }
    100. [Fact]
    101. [Trait("test", "integration")]
    102. public async void ExecuteFetchLukeQuery()
    103. {
    104. // Given
    105. const string query = @"{
    106. ""query"":
    107. ""query FetchLukeQuery {
    108. human(id: ""1000"") {
    109. name
    110. }
    111. }
    112. }""
    113. }";
    114. var content = new StringContent(query, Encoding.UTF8, "application/json");
    115. // When
    116. var response = await _client.PostAsync("/graphql", content);
    117. // Then
    118. response.EnsureSuccessStatusCode();
    119. var responseString = await response.Content.ReadAsStringAsync();
    120. Assert.NotNull(responseString);
    121. var jobj = JObject.Parse(responseString);
    122. Assert.NotNull(jobj);
    123. Assert.Equal("Luke Skywalker", (string)jobj["data"]["human"]["name"]);
    124. }
    125. [Fact]
    126. [Trait("test", "integration")]
    127. public async void ExecuteFetchLukeAliased()
    128. {
    129. // Given
    130. const string query = @"{
    131. ""query"":
    132. ""query FetchLukeAliased {
    133. luke: human(id: ""1000"") {
    134. name
    135. }
    136. }
    137. }""
    138. }";
    139. var content = new StringContent(query, Encoding.UTF8, "application/json");
    140. // When
    141. var response = await _client.PostAsync("/graphql", content);
    142. // Then
    143. response.EnsureSuccessStatusCode();
    144. var responseString = await response.Content.ReadAsStringAsync();
    145. Assert.NotNull(responseString);
    146. var jobj = JObject.Parse(responseString);
    147. Assert.NotNull(jobj);
    148. Assert.Equal("Luke Skywalker", (string)jobj["data"]["human"]["name"]);
    149. }
    150. [Fact]
    151. [Trait("test", "integration")]
    152. public async void ExecuteFetchLukeAndLeiaAliased()
    153. {
    154. // Given
    155. const string query = @"{
    156. ""query"":
    157. ""query FetchLukeAliased {
    158. luke: human(id: ""1000"") {
    159. name
    160. }
    161. leia: human(id: ""1003"") {
    162. name
    163. }
    164. }
    165. }""
    166. }";
    167. var content = new StringContent(query, Encoding.UTF8, "application/json");
    168. // When
    169. var response = await _client.PostAsync("/graphql", content);
    170. // Then
    171. response.EnsureSuccessStatusCode();
    172. var responseString = await response.Content.ReadAsStringAsync();
    173. Assert.NotNull(responseString);
    174. var jobj = JObject.Parse(responseString);
    175. Assert.NotNull(jobj);
    176. Assert.Equal("Luke Skywalker", (string)jobj["data"]["luke"]["name"]);
    177. Assert.Equal("Leia Organa", (string)jobj["data"]["leia"]["name"]);
    178. }
    179. [Fact]
    180. [Trait("test", "integration")]
    181. public async void ExecuteDuplicateFields()
    182. {
    183. // Given
    184. const string query = @"{
    185. ""query"":
    186. ""query DuplicateFields {
    187. luke: human(id: ""1000"") {
    188. name
    189. homePlanet
    190. }
    191. leia: human(id: ""1003"") {
    192. name
    193. homePlanet
    194. }
    195. }
    196. }""
    197. }";
    198. var content = new StringContent(query, Encoding.UTF8, "application/json");
    199. // When
    200. var response = await _client.PostAsync("/graphql", content);
    201. // Then
    202. response.EnsureSuccessStatusCode();
    203. var responseString = await response.Content.ReadAsStringAsync();
    204. Assert.NotNull(responseString);
    205. var jobj = JObject.Parse(responseString);
    206. Assert.NotNull(jobj);
    207. Assert.Equal("Luke Skywalker", (string)jobj["data"]["luke"]["name"]);
    208. Assert.Equal("Tatooine", (string)jobj["data"]["luke"]["homePlanet"]);
    209. Assert.Equal("Leia Organa", (string)jobj["data"]["leia"]["name"]);
    210. Assert.Equal("Alderaan", (string)jobj["data"]["leia"]["homePlanet"]);
    211. }
    212. [Fact]
    213. [Trait("test", "integration")]
    214. public async void ExecuteUseFragment()
    215. {
    216. // Given
    217. const string query = @"{
    218. ""query"":
    219. ""query UseFragment {
    220. luke: human(id: ""1000"") {
    221. ...HumanFragment
    222. }
    223. leia: human(id: ""1003"") {
    224. ...HumanFragment
    225. }
    226. }
    227. fragment HumanFragment on Human {
    228. name
    229. homePlanet
    230. }
    231. }""
    232. }";
    233. var content = new StringContent(query, Encoding.UTF8, "application/json");
    234. // When
    235. var response = await _client.PostAsync("/graphql", content);
    236. // Then
    237. response.EnsureSuccessStatusCode();
    238. var responseString = await response.Content.ReadAsStringAsync();
    239. Assert.NotNull(responseString);
    240. var jobj = JObject.Parse(responseString);
    241. Assert.NotNull(jobj);
    242. Assert.Equal("Luke Skywalker", (string)jobj["data"]["luke"]["name"]);
    243. Assert.Equal("Tatooine", (string)jobj["data"]["luke"]["homePlanet"]);
    244. Assert.Equal("Leia Organa", (string)jobj["data"]["leia"]["name"]);
    245. Assert.Equal("Alderaan", (string)jobj["data"]["leia"]["homePlanet"]);
    246. }
    247. [Fact]
    248. [Trait("test", "integration")]
    249. public async void ExecuteCheckTypeOfR2()
    250. {
    251. // Given
    252. const string query = @"{
    253. ""query"":
    254. ""query CheckTypeOfR2 {
    255. hero {
    256. __typename
    257. name
    258. }
    259. }
    260. }""
    261. }";
    262. var content = new StringContent(query, Encoding.UTF8, "application/json");
    263. // When
    264. var response = await _client.PostAsync("/graphql", content);
    265. // Then
    266. response.EnsureSuccessStatusCode();
    267. var responseString = await response.Content.ReadAsStringAsync();
    268. Assert.NotNull(responseString);
    269. var jobj = JObject.Parse(responseString);
    270. Assert.NotNull(jobj);
    271. Assert.Equal("Droid", (string)jobj["data"]["hero"]["__typename"]);
    272. Assert.Equal("R2-D2", (string)jobj["data"]["hero"]["name"]);
    273. }
    274. [Fact]
    275. [Trait("test", "integration")]
    276. public async void ExecuteCheckTypeOfLuke()
    277. {
    278. // Given
    279. const string query = @"{
    280. ""query"":
    281. ""query CheckTypeOfLuke {
    282. hero(episode: EMPIRE) {
    283. __typename
    284. name
    285. }
    286. }
    287. }""
    288. }";
    289. var content = new StringContent(query, Encoding.UTF8, "application/json");
    290. // When
    291. var response = await _client.PostAsync("/graphql", content);
    292. // Then
    293. response.EnsureSuccessStatusCode();
    294. var responseString = await response.Content.ReadAsStringAsync();
    295. Assert.NotNull(responseString);
    296. var jobj = JObject.Parse(responseString);
    297. Assert.NotNull(jobj);
    298. Assert.Equal("Human", (string)jobj["data"]["hero"]["__typename"]);
    299. Assert.Equal("Luke Skywalker", (string)jobj["data"]["hero"]["name"]);
    300. }
    301. }
    302. }

    facebook-spec-queries-tdd-integration-tests-failed