Home

Awesome

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

Build Status

Examples

Roadmap

Tutorials

Basic

"runtimes": {
   "win10-x64": { }
}
namespace StarWars.Core.Models
{
    public class Droid
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
using GraphQL.Types;
using StarWars.Core.Models;

namespace StarWars.Api.Models
{
    public class DroidType : ObjectGraphType<Droid>
    {
        public DroidType()
        {
            Field(x => x.Id).Description("The Id of the Droid.");
            Field(x => x.Name, nullable: true).Description("The name of the Droid.");
        }
    }
}
using GraphQL.Types;
using StarWars.Core.Models;

namespace StarWars.Api.Models
{
    public class StarWarsQuery : ObjectGraphType
    {
        public StarWarsQuery()
        {
            Field<DroidType>(
              "hero",
              resolve: context => new Droid { Id = 1, Name = "R2-D2" }
            );
        }
    }
}
namespace StarWars.Api.Models
{
    public class GraphQLQuery
    {
        public string OperationName { get; set; }
        public string NamedQuery { get; set; }
        public string Query { get; set; }
        public string Variables { get; set; }
    }
}
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<IActionResult> Post([FromBody] GraphQLQuery query)
        {
            var schema = new Schema { Query = new StarWarsQuery() };

            var result = await new DocumentExecuter().ExecuteAsync(_ =>
            {
                _.Schema = schema;
                _.Query = query.Query;

            }).ConfigureAwait(false);

            if (result.Errors?.Count > 0)
            {
                return BadRequest();
            }

            return Ok(result);
        }
    }
}
using StarWars.Core.Models;
using System.Threading.Tasks;

namespace StarWars.Core.Data
{
    public interface IDroidRepository
    {
        Task<Droid> Get(int id);
    }
}
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<Droid> _droids = new List<Droid> {
            new Droid { Id = 1, Name = "R2-D2" }
        };

        public Task<Droid> Get(int id)
        {
            return Task.FromResult(_droids.FirstOrDefault(droid => droid.Id == id));
        }
    }
}
using GraphQL.Types;
using StarWars.Core.Data;

namespace StarWars.Api.Models
{
    public class StarWarsQuery : ObjectGraphType
    {
        private IDroidRepository _droidRepository { get; set; }

        public StarWarsQuery(IDroidRepository _droidRepository)
        {
            Field<DroidType>(
              "hero",
              resolve: context => _droidRepository.Get(1)
            );
        }
    }
}
// ...
public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
{
    var schema = new Schema { Query = new StarWarsQuery(new DroidRepository()) };
// ...
// ...
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddTransient<StarWarsQuery>();
    services.AddTransient<IDroidRepository, DroidRepository>();
}
// ...
// ...
public class GraphQLController : Controller
{
    private StarWarsQuery _starWarsQuery { get; set; }

    public GraphQLController(StarWarsQuery starWarsQuery)
    {
        _starWarsQuery = starWarsQuery;
    }

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
    {
        var schema = new Schema { Query = _starWarsQuery };
// ...
using Microsoft.EntityFrameworkCore;
using StarWars.Core.Models;

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

        public DbSet<Droid> Droids { get; set; }
    }
}
{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "ConnectionStrings": {
    "StarWarsDatabaseConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=StarWars;Integrated Security=SSPI;integrated security=true;MultipleActiveResultSets=True;"
  }
}
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; }

        public DroidRepository(StarWarsContext db)
        {
            _db = db;
        }

        public Task<Droid> Get(int id)
        {
            return _db.Droids.FirstOrDefaultAsync(droid => droid.Id == id);
        }
    }
}
using StarWars.Core.Models;
using System.Linq;

namespace StarWars.Data.EntityFramework.Seed
{
    public static class StarWarsSeedData
    {
        public static void EnsureSeedData(this StarWarsContext db)
        {
            if (!db.Droids.Any())
            {
                var droid = new Droid
                {
                    Name = "R2-D2"
                };
                db.Droids.Add(droid);
                db.SaveChanges();
            }
        }
    }
}
// ...
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddTransient<StarWarsQuery>();
    services.AddTransient<IDroidRepository, DroidRepository>();
    services.AddDbContext<StarWarsContext>(options => 
        options.UseSqlServer(Configuration["ConnectionStrings:StarWarsDatabaseConnection"])
    );
}

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

    app.UseMvc();

    db.EnsureSeedData();
}
// ...

Entity Framework Migrations

"tools": {
    "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.1.0-preview4-final"
}
dotnet ef migrations add Inital -o .\EntityFramework\Migrations

dotnet-ef-migrations

GrahpiQL

{
  "version": "1.0.0",
  "name": "starwars-graphiql",
  "private": true,
  "scripts": {
    "start": "webpack --progress"
  },
  "dependencies": {
    "graphiql": "^0.7.8",
    "graphql": "^0.7.0",
    "isomorphic-fetch": "^2.1.1",
    "react": "^15.3.1",
    "react-dom": "^15.3.1"
  },
  "devDependencies": {
    "babel": "^5.6.14",
    "babel-loader": "^5.3.2",
    "css-loader": "^0.24.0",
    "extract-text-webpack-plugin": "^1.0.1",
    "postcss-loader": "^0.10.1",
    "style-loader": "^0.13.1",
    "webpack": "^1.13.0"
  }
}
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');

var output = './wwwroot';

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

    output: {
        path: output,
        filename: '[name].js'
    },

    resolve: {
        extensions: ['', '.js', '.json']
    },

    module: {
        loaders: [
          { test: /\.js/, loader: 'babel', exclude: /node_modules/ },
          { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader') }
        ]
    },

    plugins: [
      new ExtractTextPlugin('style.css', { allChunks: true })
    ]
};
  "-vs-binding": { "AfterBuild": [ "start" ] }
// ...
public class GraphQLController : Controller
{
    // ...
    [HttpGet]
    public IActionResult Index()
    {
        return View();
    }    
// ...
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>GraphiQL</title>
    <link rel="stylesheet" href="~/style.css" />
</head>
<body>
    <div id="app"></div>
    <script src="~/bundle.js" type="text/javascript"></script>
</body>
</html>

Unit Tests

{
  "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": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },

  "runtimes": {
    "win10-x64": {}
  }
}
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();
        }

        [Fact]
        public async void ReturnR2D2DroidGivenIdOf1()
        {
            // When
            var droid = await _droidRepository.Get(1);

            // Then
            Assert.NotNull(droid);
            Assert.Equal("WRONG_NAME", droid.Name);
        }
    }
}
// ...
[Fact]
public async void ReturnR2D2DroidGivenIdOf1()
{
    // When
    var droid = await _droidRepository.Get(1);

    // Then
    Assert.NotNull(droid);
    Assert.Equal("R2-D2", droid.Name);
}
// ...
{
  "dependencies": {
    "StarWars.Core": {
      "target": "project"
    }
  }
}
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<StarWarsContext>()
                .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);
        }

        [Fact]
        public async void ReturnR2D2DroidGivenIdOf1()
        {
            // When
            var droid = await _droidRepository.Get(1);

            // Then
            Assert.NotNull(droid);
            Assert.Equal("R2-D2", droid.Name);
        }
    }
}

Visual Studio 2017 RC upgrade

{
  "dependencies": {
    // remove this:
    "StarWars.Data": {
      "target": "project"
    },
    "StarWars.Core": {
      "target": "project"
    }
    // ...
  }
}

Integration Tests

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>    
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
  <!--...-->
</Project>
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;

        public GraphQLControllerShould()
        {
            _server = new TestServer(new WebHostBuilder()
                .UseEnvironment("Test")
                .UseStartup<Startup>()
            );
            _client = _server.CreateClient();
        }

        [Fact]
        public async void ReturnR2D2Droid()
        {
            // Given
            var query = @"{
                ""query"": ""query { hero { id name } }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.Contains("R2-D2", responseString);
        }
    }
}

Logs

public void Configure(IApplicationBuilder app, IHostingEnvironment env,
                              ILoggerFactory loggerFactory, StarWarsContext db)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();
    // ...
}
public override string ToString()
{
    var builder = new StringBuilder();
    builder.AppendLine();
    if (!string.IsNullOrWhiteSpace(OperationName))
    {
        builder.AppendLine($"OperationName = {OperationName}");
    }
    if (!string.IsNullOrWhiteSpace(NamedQuery))
    {
        builder.AppendLine($"NamedQuery = {NamedQuery}");
    }
    if (!string.IsNullOrWhiteSpace(Query))
    {
        builder.AppendLine($"Query = {Query}");
    }
    if (!string.IsNullOrWhiteSpace(Variables))
    {
        builder.AppendLine($"Variables = {Variables}");
    }

    return builder.ToString();
}
public class GraphQLController : Controller
{
    // ...
    private readonly ILogger _logger;

    public GraphQLController(IDocumentExecuter documentExecuter, ISchema schema, ILogger<GraphQLController> logger)
    {
        // ...
        _logger = logger;
    }

    [HttpGet]
    public IActionResult Index()
    {
        _logger.LogInformation("Got request for GraphiQL. Sending GUI back");
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
    {
        // ...
        if (result.Errors?.Count > 0)
        {
            _logger.LogError("GraphQL errors: {0}", result.Errors);
            return BadRequest(result);
        }

        _logger.LogDebug("GraphQL execution result: {result}", JsonConvert.SerializeObject(result.Data));
        return Ok(result);
    }
}
namespace StarWars.Data.EntityFramework.Repositories
{
    public class DroidRepository : IDroidRepository
    {
        private StarWarsContext _db { get; set; }
        private readonly ILogger _logger;

        public DroidRepository(StarWarsContext db, ILogger<DroidRepository> logger)
        {
            _db = db;
            _logger = logger;
        }

        public Task<Droid> Get(int id)
        {
            _logger.LogInformation("Get droid with id = {id}", id);
            return _db.Droids.FirstOrDefaultAsync(droid => droid.Id == id);
        }
    }
}
namespace StarWars.Data.EntityFramework
{
    public class StarWarsContext : DbContext
    {
        public readonly ILogger _logger;

        public StarWarsContext(DbContextOptions options, ILogger<StarWarsContext> logger)
            : base(options)
        {
            _logger = logger;
            // ...
        }
    }
}
namespace StarWars.Data.EntityFramework.Seed
{
    public static class StarWarsSeedData
    {
        public static void EnsureSeedData(this StarWarsContext db)
        {
            db._logger.LogInformation("Seeding database");
            if (!db.Droids.Any())
            {
                db._logger.LogInformation("Seeding droids");
                // ...
            }
        }
    }
}
public class GraphQLControllerShould
{
    public GraphQLControllerShould()
    {
        // ...
        var logger = new Mock<ILogger<GraphQLController>>();
        _graphqlController = new GraphQLController(documentExecutor.Object, schema.Object, logger.Object);
    }
    // ...
}
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<ILogger<StarWarsContext>>();
            // ...
            using (var context = new StarWarsContext(options, dbLogger.Object))
            {
                // ...
            }
            // ...
            var repoLogger = new Mock<ILogger<DroidRepository>>();
            _droidRepository = new DroidRepository(starWarsContext, repoLogger.Object);
        }
    }
}

Code Coverage

C:\Users\Jacek_Kosciesza\.nuget\packages\opencover\4.6.519\tools\
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <!--...-->
    <DebugType>Full</DebugType>
  </PropertyGroup>
  <!--...-->
</Project>
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

open-cover-unit-tests-results

mkdir coverage\unit
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
ReportGenerator.exe -reports:coverage/unit/coverage.xml -targetdir:coverage/unit -verbosity:Error
start .\coverage\unit\index.htm

Continous Integration

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

vsts-setup-projects-test-build-setp

Advanced

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

namespace StarWars.Core.Models
{
    public class Episode
    {
        public int  Id  { get; set; }
        public string Title { get; set; }
        public virtual ICollection<CharacterEpisode> CharacterEpisodes { get; set; }
    }
}
namespace StarWars.Core.Models
{
    public class Planet
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public ICollection<Human> Humans { get; set; }
    }
}
namespace StarWars.Core.Models
{
    public class Character
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public virtual ICollection<CharacterEpisode> CharacterEpisodes { get; set; }
        public virtual ICollection<CharacterFriend> CharacterFriends { get; set; }
        public virtual ICollection<CharacterFriend> FriendCharacters { get; set; }
    }
}
namespace StarWars.Core.Models
{
    public class CharacterEpisode
    {
        public int CharacterId { get; set; }
        public Character Character { get; set; }

        public int EpisodeId { get; set; }
        public Episode Episode { get; set; }
    }
}
namespace StarWars.Core.Models
{
    public class CharacterFriend
    {
        public int CharacterId { get; set; }
        public Character Character { get; set; }

        public int FriendId { get; set; }
        public Character Friend { get; set; }
    }
}
namespace StarWars.Core.Models
{
    public class Droid : Character
    {
        public string PrimaryFunction { get; set; }
    }
}
namespace StarWars.Core.Models
{
    public class Human : Character
    {
        public Planet HomePlanet { get; set; }
    }
}
namespace StarWars.Data.EntityFramework
{
    public class StarWarsContext : DbContext
    {
        // ...
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // https://docs.microsoft.com/en-us/ef/core/modeling/relationships
            // http://stackoverflow.com/questions/38520695/multiple-relationships-to-the-same-table-in-ef7core

            // episodes
            modelBuilder.Entity<Episode>().HasKey(c => c.Id);
            modelBuilder.Entity<Episode>().Property(e => e.Id).ValueGeneratedNever();

            // planets
            modelBuilder.Entity<Planet>().HasKey(c => c.Id);
            modelBuilder.Entity<Planet>().Property(e => e.Id).ValueGeneratedNever();

            // characters
            modelBuilder.Entity<Character>().HasKey(c => c.Id);
            modelBuilder.Entity<Character>().Property(e => e.Id).ValueGeneratedNever();

            // characters-friends
            modelBuilder.Entity<CharacterFriend>().HasKey(t => new { t.CharacterId, t.FriendId});

            modelBuilder.Entity<CharacterFriend>()
                .HasOne(cf => cf.Character)
                .WithMany(c => c.CharacterFriends)
                .HasForeignKey(cf => cf.CharacterId)                
                .OnDelete(DeleteBehavior.Restrict);               

            modelBuilder.Entity<CharacterFriend>()
                .HasOne(cf => cf.Friend)
                .WithMany(t => t.FriendCharacters)
                .HasForeignKey(cf => cf.FriendId)
                .OnDelete(DeleteBehavior.Restrict);

            // characters-episodes
            modelBuilder.Entity<CharacterEpisode>().HasKey(t => new { t.CharacterId, t.EpisodeId });

            modelBuilder.Entity<CharacterEpisode>()
                .HasOne(cf => cf.Character)
                .WithMany(c => c.CharacterEpisodes)
                .HasForeignKey(cf => cf.CharacterId)
                .OnDelete(DeleteBehavior.Restrict);

            modelBuilder.Entity<CharacterEpisode>()
                .HasOne(cf => cf.Episode)
                .WithMany(t => t.CharacterEpisodes)
                .HasForeignKey(cf => cf.EpisodeId)
                .OnDelete(DeleteBehavior.Restrict);

            // humans
            modelBuilder.Entity<Human>().HasOne(h => h.HomePlanet).WithMany(p => p.Humans);
        }

        public virtual DbSet<Episode> Episodes { get; set; }
        public virtual DbSet<Planet> Planets { get; set; }
        public virtual DbSet<Character> Characters { get; set; }
        public virtual DbSet<CharacterFriend> CharacterFriends { get; set; }
        public virtual DbSet<CharacterEpisode> CharacterEpisodes { get; set; }
        public virtual DbSet<Droid> Droids { get; set; }
        public virtual DbSet<Human> Humans { get; set; }
    }
}
namespace StarWars.Data.EntityFramework.Seed
{
    public static class StarWarsSeedData
    {
        public static void EnsureSeedData(this StarWarsContext db)
        {
            db._logger.LogInformation("Seeding database");

            // episodes
            var newhope = new Episode { Id = 4, Title = "NEWHOPE" };
            var empire = new Episode { Id = 5, Title = "EMPIRE" };
            var jedi = new Episode { Id = 6, Title = "JEDI" };
            var episodes = new List<Episode>
            {
                newhope,
                empire,
                jedi,
            };
            if (!db.Episodes.Any())
            {
                db._logger.LogInformation("Seeding episodes");
                db.Episodes.AddRange(episodes);
                db.SaveChanges();
            }

            // planets
            var tatooine = new Planet { Id = 1, Name = "Tatooine" };
            var alderaan = new Planet { Id = 2, Name = "Alderaan" };
            var planets = new List<Planet>
            {
                tatooine,
                alderaan
            };
            if (!db.Planets.Any())
            {
                db._logger.LogInformation("Seeding planets");
                db.Planets.AddRange(planets);
                db.SaveChanges();
            }

            // humans
            var luke = new Human
            {
                Id = 1000,
                Name = "Luke Skywalker",
                CharacterEpisodes = new List<CharacterEpisode>
                {
                    new CharacterEpisode { Episode = newhope },
                    new CharacterEpisode { Episode = empire },
                    new CharacterEpisode { Episode = jedi }
                },
                HomePlanet = tatooine
            };
            var vader = new Human
            {
                Id = 1001,
                Name = "Darth Vader",
                CharacterEpisodes = new List<CharacterEpisode>
                {
                    new CharacterEpisode { Episode = newhope },
                    new CharacterEpisode { Episode = empire },
                    new CharacterEpisode { Episode = jedi }
                },
                HomePlanet = tatooine
            };
            var han = new Human
            {
                Id = 1002,
                Name = "Han Solo",
                CharacterEpisodes = new List<CharacterEpisode>
                {
                    new CharacterEpisode { Episode = newhope },
                    new CharacterEpisode { Episode = empire },
                    new CharacterEpisode { Episode = jedi }
                },
                HomePlanet = tatooine
            };
            var leia = new Human
            {
                Id = 1003,
                Name = "Leia Organa",
                CharacterEpisodes = new List<CharacterEpisode>
                {
                    new CharacterEpisode { Episode = newhope },
                    new CharacterEpisode { Episode = empire },
                    new CharacterEpisode { Episode = jedi }
                },
                HomePlanet = alderaan
            };
            var tarkin = new Human
            {
                Id = 1004,
                Name = "Wilhuff Tarkin",
                CharacterEpisodes = new List<CharacterEpisode>
                {
                    new CharacterEpisode { Episode = newhope }                    
                },
            };
            var humans = new List<Human>
            {
                luke,
                vader,
                han,
                leia,
                tarkin
            };
            if (!db.Humans.Any())
            {
                db._logger.LogInformation("Seeding humans");                
                db.Humans.AddRange(humans);
                db.SaveChanges();
            }

            // droids
            var threepio = new Droid
            {
                Id = 2000,
                Name = "C-3PO",
                CharacterEpisodes = new List<CharacterEpisode>
                {
                    new CharacterEpisode { Episode = newhope },
                    new CharacterEpisode { Episode = empire },
                    new CharacterEpisode { Episode = jedi }
                },
                PrimaryFunction = "Protocol"
            };
            var artoo = new Droid
            {
                Id = 2001,
                Name = "R2-D2",
                CharacterEpisodes = new List<CharacterEpisode>
                {
                    new CharacterEpisode { Episode = newhope },
                    new CharacterEpisode { Episode = empire },
                    new CharacterEpisode { Episode = jedi }
                },
                PrimaryFunction = "Astromech"
            };
            var droids = new List<Droid>
            {
                threepio,
                artoo
            };
            if (!db.Droids.Any())
            {
                db._logger.LogInformation("Seeding droids");
                db.Droids.AddRange(droids);
                db.SaveChanges();
            }

            // update character's friends
            luke.CharacterFriends = new List<CharacterFriend>
            {
                new CharacterFriend { Friend = han },
                new CharacterFriend { Friend = leia },
                new CharacterFriend { Friend = threepio },
                new CharacterFriend { Friend = artoo }
            };
            vader.CharacterFriends = new List<CharacterFriend>
            {
                new CharacterFriend { Friend = tarkin }
            };
            han.CharacterFriends = new List<CharacterFriend>
            {
                new CharacterFriend { Friend = luke },
                new CharacterFriend { Friend = leia },
                new CharacterFriend { Friend = artoo }
            };
            leia.CharacterFriends = new List<CharacterFriend>
            {
                new CharacterFriend { Friend = luke },
                new CharacterFriend { Friend = han },
                new CharacterFriend { Friend = threepio },
                new CharacterFriend { Friend = artoo }
            };
            tarkin.CharacterFriends = new List<CharacterFriend>
            {
                new CharacterFriend { Friend = vader }
            };
            threepio.CharacterFriends = new List<CharacterFriend>
            {
                new CharacterFriend { Friend = luke },
                new CharacterFriend { Friend = han },
                new CharacterFriend { Friend = leia },
                new CharacterFriend { Friend = artoo }
            };
            artoo.CharacterFriends = new List<CharacterFriend>
            {
                new CharacterFriend { Friend = luke },
                new CharacterFriend { Friend = han },
                new CharacterFriend { Friend = leia }
            };
            var characters = new List<Character>
            {
                luke,
                vader,
                han,
                leia,
                tarkin,
                threepio,
                artoo
            };
            if (!db.CharacterFriends.Any())
            {
                db._logger.LogInformation("Seeding character's friends");
                db.Characters.UpdateRange(characters);
                db.SaveChanges();
            }
        }
    }
}
namespace StarWars.Tests.Integration.Data.EntityFramework
{
    public class StarWarsContextShould
    {
        [Fact]
        public async void ReturnR2D2Droid()
        {
            // Given
            using (var db = new StarWarsContext())
            {
                // When
                var r2d2 = await db.Droids
                    .Include("CharacterEpisodes.Episode")
                    .Include("CharacterFriends.Friend")
                    .FirstOrDefaultAsync(d => d.Id == 2001);

                // Then
                Assert.NotNull(r2d2);
                Assert.Equal("R2-D2", r2d2.Name);
                Assert.Equal("Astromech", r2d2.PrimaryFunction);
                var episodes = r2d2.CharacterEpisodes.Select(e => e.Episode.Title);
                Assert.Equal(new string[] { "NEWHOPE", "EMPIRE", "JEDI" }, episodes);
                var friends = r2d2.CharacterFriends.Select(e => e.Friend.Name);
                Assert.Equal(new string[] { "Luke Skywalker", "Han Solo", "Leia Organa" }, friends);
            }
        }
    }
}
namespace StarWars.Api.Models
{
    public class StarWarsQuery : ObjectGraphType
    {
        // ...
        public StarWarsQuery(IDroidRepository _droidRepository)
        {
            Field<DroidType>(
              "hero",
              resolve: context => _droidRepository.Get(2001)
            );
        }
    }
}

Base/generic repository

namespace StarWars.Core.Data
{
    public interface IEntity<TKey>
    {
        TKey Id { get; set; }
    }
}
namespace StarWars.Core.Models
{
    public class Character : IEntity<int>
    {
        // ...
    }
}
namespace StarWars.Core.Models
{
    public class Episode : IEntity<int>
    {
        // ...
    }
}
namespace StarWars.Core.Models
{
    public class Planet : IEntity<int>
    {
        // ...
    }
}
namespace StarWars.Core.Data
{
    public interface IBaseRepository<TEntity, in TKey>
        where TEntity : class
    {
        Task<List<TEntity>> GetAll();
        Task<TEntity> Get(TKey id);
        TEntity Add(TEntity entity);
        void AddRange(IEnumerable<TEntity> entities);
        void Delete(TKey id);
        void Update(TEntity entity);
        Task<bool> SaveChangesAsync();
    }
}
namespace StarWars.Data.EntityFramework.Repositories
{
    public abstract class BaseRepository<TEntity, TKey> : IBaseRepository<TEntity, TKey>
        where TEntity : class, IEntity<TKey>, new()
    {
        protected DbContext _db;
        protected readonly ILogger _logger;

        protected BaseRepository() { }

        protected BaseRepository(DbContext db, ILogger logger)
        {
            _db = db;
            _logger = logger;
        }

        public virtual Task<List<TEntity>> GetAll()
        {
            return _db.Set<TEntity>().ToListAsync();
        }

        public virtual Task<TEntity> Get(TKey id)
        {
            _logger.LogInformation("Get {type} with id = {id}", typeof(TEntity).Name, id);
            return _db.Set<TEntity>().SingleOrDefaultAsync(c => c.Id.Equals(id));
        }

        public virtual TEntity Add(TEntity entity)
        {
            _db.Set<TEntity>().Add(entity);
            return entity;
        }

        public void AddRange(IEnumerable<TEntity> entities)
        {
            _db.Set<TEntity>().AddRange(entities);
        }

        public virtual void Delete(TKey id)
        {
            var entity = new TEntity { Id = id };
            _db.Set<TEntity>().Attach(entity);
            _db.Set<TEntity>().Remove(entity);
        }

        public virtual async Task<bool> SaveChangesAsync()
        {
            return (await _db.SaveChangesAsync()) > 0;
        }

        public virtual void Update(TEntity entity)
        {
            _db.Set<TEntity>().Attach(entity);
            _db.Entry(entity).State = EntityState.Modified;
        }
    }
}
namespace StarWars.Core.Data
{
    public interface IDroidRepository : IBaseRepository<Droid, int> { }
}
namespace StarWars.Data.EntityFramework.Repositories
{
    public class DroidRepository : BaseRepository<Droid, int>, IDroidRepository
    {
        public DroidRepository() { }

        public DroidRepository(StarWarsContext db, ILogger<DroidRepository> logger)
            : base(db, logger)
        {
        }
    }
}
namespace StarWars.Data.InMemory
{
    public class DroidRepository : IDroidRepository
    {
        private readonly ILogger _logger;

        public DroidRepository() { }

        public DroidRepository(ILogger<DroidRepository> logger)
        {
            _logger = logger;
        }

        private List<Droid> _droids = new List<Droid> {
            new Droid { Id = 1, Name = "R2-D2" }
        };

        public Task<Droid> Get(int id)
        {
            _logger.LogInformation("Get droid with id = {id}", id);
            return Task.FromResult(_droids.FirstOrDefault(droid => droid.Id == id));
        }

        // ...
        // rest of the methods are not implemented
        // for now they are just throwing  NotImplementedException       
    }
}

Visual Studio 2017 RTM upgrade

Install-Package Microsoft.NETCore.App

package-manager-console-netcore-app-upgrade

namespace StarWars.Tests.Unit.Api.Models
{
    public class DroidTypeShould
    {
        [Fact]
        public void HaveIdAndNameFields()
        {
            // When
            var droidType = new DroidType();

            // Then
            Assert.NotNull(droidType);
            Assert.True(droidType.HasField("Id"));
            Assert.True(droidType.HasField("Name"));
        }
    }
}

Repositories

namespace StarWars.Core.Data
{
    public interface IHumanRepository : IBaseRepository<Human, int> { }
}
namespace StarWars.Data.EntityFramework.Repositories
{
    public class HumanRepository : BaseRepository<Human, int>, IHumanRepository
    {
        public HumanRepository() { }

        public HumanRepository(StarWarsContext db, ILogger<HumanRepository> logger)
            : base(db, logger)
        {
        }
    }
}
namespace StarWars.Core.Data
{
    public interface IBaseRepository<TEntity, in TKey>
        where TEntity : class
    {
        // ...
        Task<List<TEntity>> GetAll(string include);
        Task<List<TEntity>> GetAll(IEnumerable<string> includes);
        
        // ...

        Task<TEntity> Get(TKey id, string include);
        Task<TEntity> Get(TKey id, IEnumerable<string> includes);
        // ...
    }
}
namespace StarWars.Data.EntityFramework.Repositories
{
    public abstract class BaseRepository<TEntity, TKey> : IBaseRepository<TEntity, TKey>
        where TEntity : class, IEntity<TKey>, new()
    {
        // ...
        public Task<List<TEntity>> GetAll(string include)
        {
            _logger.LogInformation("Get all {type}s (including {include})", typeof(TEntity).Name, include);
            return _db.Set<TEntity>().Include(include).ToListAsync();
        }

        public Task<List<TEntity>> GetAll(IEnumerable<string> includes)
        {
            _logger.LogInformation("Get all {type}s (including [{includes}])", typeof(TEntity).Name, string.Join(",", includes));
            var query = _db.Set<TEntity>().AsQueryable();
            query = includes.Aggregate(query, (current, include) => current.Include(include));
            return query.ToListAsync();
        }

        // ...

        public Task<TEntity> Get(TKey id, string include)
        {
            _logger.LogInformation("Get {type} with id = {id} (including {include})", typeof(TEntity).Name, id, include);
            return _db.Set<TEntity>().Include(include).SingleOrDefaultAsync(c => c.Id.Equals(id));
        }

        public Task<TEntity> Get(TKey id, IEnumerable<string> includes)
        {
            _logger.LogInformation("Get {type} with id = {id} (including [{include}])", typeof(TEntity).Name, id, string.Join(",", includes));
            var query = _db.Set<TEntity>().AsQueryable();
            query = includes.Aggregate(query, (current, include) => current.Include(include));
            return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
        }

        // ...
    }
}
namespace StarWars.Tests.Unit.Data.EntityFramework.Repositories
{
    public class HumanRepositoryShould
    {
        private readonly HumanRepository _humanRepository;
        private DbContextOptions<StarWarsContext> _options;
        private Mock<ILogger<StarWarsContext>> _dbLogger;
        public HumanRepositoryShould()
        {
            // Given
            _dbLogger = new Mock<ILogger<StarWarsContext>>();
            _options = new DbContextOptionsBuilder<StarWarsContext>()
                .UseInMemoryDatabase(databaseName: "StarWars_HumanRepositoryShould")
                .Options;
            using (var context = new StarWarsContext(_options, _dbLogger.Object))
            {
                context.EnsureSeedData();
            }
            var starWarsContext = new StarWarsContext(_options, _dbLogger.Object);
            var repoLogger = new Mock<ILogger<HumanRepository>>();
            _humanRepository = new HumanRepository(starWarsContext, repoLogger.Object);
        }

        [Fact]
        public async void ReturnLukeGivenIdOf1000()
        {
            // When
            var luke = await _humanRepository.Get(1000);

            // Then
            Assert.NotNull(luke);
            Assert.Equal("Luke Skywalker", luke.Name);
        }

        [Fact]
        public async void ReturnLukeFriendsAndEpisodes()
        {
            // When
            var character = await _humanRepository.Get(1000, includes: new[] { "CharacterEpisodes.Episode", "CharacterFriends.Friend" });

            // Then
            Assert.NotNull(character);
            Assert.NotNull(character.CharacterEpisodes);
            var episodes = character.CharacterEpisodes.Select(e => e.Episode.Title);
            Assert.Equal(new[] { "NEWHOPE", "EMPIRE", "JEDI" }, episodes);
            Assert.NotNull(character.CharacterFriends);
            var friends = character.CharacterFriends.Select(e => e.Friend.Name);
            Assert.Equal(new[] { "Han Solo", "Leia Organa", "C-3PO", "R2-D2" }, friends);
        }

        [Fact]
        public async void ReturnLukesHomePlanet()
        {
            // When
            var luke = await _humanRepository.Get(1000, include: "HomePlanet");

            // Then
            Assert.NotNull(luke);
            Assert.NotNull(luke.HomePlanet);
            Assert.Equal("Tatooine", luke.HomePlanet.Name);
        }

        [Fact]
        public async void AddNewHuman()
        {
            // Given
            var human10101 = new Human { Id = 10101, Name = "Human10101" };

            // When
            _humanRepository.Add(human10101);
            var saved = await _humanRepository.SaveChangesAsync();

            // Then
            Assert.True(saved);
            using (var db = new StarWarsContext(_options, _dbLogger.Object))
            {
                var human = await db.Humans.FindAsync(10101);
                Assert.NotNull(human);
                Assert.Equal(10101, human.Id);
                Assert.Equal("Human10101", human.Name);

                // Cleanup
                db.Humans.Remove(human);
                await db.SaveChangesAsync();
            }
        }

        [Fact]
        public async void UpdateExistingHuman()
        {
            // Given
            var vader = await _humanRepository.Get(1001);
            vader.Name = "Human1001";

            // When
            _humanRepository.Update(vader);
            var saved = await _humanRepository.SaveChangesAsync();

            // Then
            Assert.True(saved);
            using (var db = new StarWarsContext(_options, _dbLogger.Object))
            {
                var human = await db.Humans.FindAsync(1001);
                Assert.NotNull(human);
                Assert.Equal(1001, human.Id);
                Assert.Equal("Human1001", human.Name);

                // Cleanup
                human.Name = "Darth Vader";
                db.Humans.Update(human);
                await db.SaveChangesAsync();
            }
        }

        [Fact]
        public async void DeleteExistingHuman()
        {
            // Given
            using (var db = new StarWarsContext(_options, _dbLogger.Object))
            {
                var human10102 = new Human { Id = 10102, Name = "Human10102" };
                await db.Humans.AddAsync(human10102);
                await db.SaveChangesAsync();
            }

            // When
            _humanRepository.Delete(10102);
            var saved = await _humanRepository.SaveChangesAsync();

            // Then
            Assert.True(saved);
            using (var db = new StarWarsContext(_options, _dbLogger.Object))
            {
                var deletedHuman = await db.Humans.FindAsync(10101);
                Assert.Null(deletedHuman);
            }
        }
    }
}

GraphQL queries

namespace StarWars.Tests.Integration.Api.Controllers
{
    public class GraphQLControllerShould
    {
        // ...
        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteHeroNameQuery()
        {
            // Given
            const string query = @"{
                ""query"": 
                    ""query HeroNameQuery {
                        hero {
                            name
                        }
                    }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal("R2-D2", (string)jobj["data"]["hero"]["name"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteHeroNameAndFriendsQuery()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query HeroNameAndFriendsQuery {
                        hero {
                            id
                            name
                            friends {
                                id
                                name
                            }
                        }
                    }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal(3, ((JArray)jobj["data"]["hero"]["friends"]).Count);
            Assert.Equal("Luke Skywalker", (string)jobj["data"]["hero"]["friends"][0]["name"]);
            Assert.Equal("Han Solo", (string)jobj["data"]["hero"]["friends"][1]["name"]);
            Assert.Equal("Leia Organa", (string)jobj["data"]["hero"]["friends"][2]["name"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteNestedQuery()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query NestedQuery {
                        hero {
                            name
                            friends {
                                name
                                appearsIn
                                friends {
                                    name
                                }
                            }
                        }
                    }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            var luke = jobj["data"]["hero"]["friends"][0];
            var episodes = ((JArray) luke["appearsIn"]).Select(e => (string)e).ToArray();
            Assert.Equal(new[] { "NEWHOPE", "EMPIRE", "JEDI" }, episodes);
            Assert.Equal(4, ((JArray)luke["friends"]).Count);
            Assert.Equal("Han Solo", (string)luke["friends"][0]["name"]);
            Assert.Equal("Leia Organa", (string)luke["friends"][1]["name"]);
            Assert.Equal("C-3PO", (string)luke["friends"][2]["name"]);
            Assert.Equal("R2-D2", (string)luke["friends"][3]["name"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteFetchLukeQuery()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query FetchLukeQuery {
                        human(id: ""1000"") {
                            name
                        }
                    }
                }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal("Luke Skywalker", (string)jobj["data"]["human"]["name"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteFetchLukeAliased()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query FetchLukeAliased {
                        luke: human(id: ""1000"") {
                            name
                        }
                    }
                }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal("Luke Skywalker", (string)jobj["data"]["human"]["name"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteFetchLukeAndLeiaAliased()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query FetchLukeAliased {
                        luke: human(id: ""1000"") {
                            name
                        }
                        leia: human(id: ""1003"") {
                            name
                        }
                    }
                }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal("Luke Skywalker", (string)jobj["data"]["luke"]["name"]);
            Assert.Equal("Leia Organa", (string)jobj["data"]["leia"]["name"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteDuplicateFields()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query DuplicateFields {
                        luke: human(id: ""1000"") {
                            name
                            homePlanet
                        }
                        leia: human(id: ""1003"") {
                            name
                            homePlanet
                        }
                    }
                }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal("Luke Skywalker", (string)jobj["data"]["luke"]["name"]);
            Assert.Equal("Tatooine", (string)jobj["data"]["luke"]["homePlanet"]);
            Assert.Equal("Leia Organa", (string)jobj["data"]["leia"]["name"]);
            Assert.Equal("Alderaan", (string)jobj["data"]["leia"]["homePlanet"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteUseFragment()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query UseFragment {
                        luke: human(id: ""1000"") {
                            ...HumanFragment
                        }
                        leia: human(id: ""1003"") {
                            ...HumanFragment
                        }
                    }

                    fragment HumanFragment on Human {
                        name
                        homePlanet
                    }
                }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal("Luke Skywalker", (string)jobj["data"]["luke"]["name"]);
            Assert.Equal("Tatooine", (string)jobj["data"]["luke"]["homePlanet"]);
            Assert.Equal("Leia Organa", (string)jobj["data"]["leia"]["name"]);
            Assert.Equal("Alderaan", (string)jobj["data"]["leia"]["homePlanet"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteCheckTypeOfR2()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query CheckTypeOfR2 {
                        hero {
                            __typename
                            name
                        } 
                    }
                }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal("Droid", (string)jobj["data"]["hero"]["__typename"]);
            Assert.Equal("R2-D2", (string)jobj["data"]["hero"]["name"]);
        }

        [Fact]
        [Trait("test", "integration")]
        public async void ExecuteCheckTypeOfLuke()
        {
            // Given
            const string query = @"{
                ""query"":
                    ""query CheckTypeOfLuke {
                       hero(episode: EMPIRE) {
                            __typename
                            name
                       }
                    }
                }""
            }";
            var content = new StringContent(query, Encoding.UTF8, "application/json");

            // When
            var response = await _client.PostAsync("/graphql", content);

            // Then
            response.EnsureSuccessStatusCode();
            var responseString = await response.Content.ReadAsStringAsync();
            Assert.NotNull(responseString);
            var jobj = JObject.Parse(responseString);
            Assert.NotNull(jobj);
            Assert.Equal("Human", (string)jobj["data"]["hero"]["__typename"]);
            Assert.Equal("Luke Skywalker", (string)jobj["data"]["hero"]["name"]);
        }
    }
}

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