Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
This repository was archived by the owner on Jan 25, 2022. It is now read-only.

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

License

NotificationsYou must be signed in to change notification settings

JacekKosciesza/StarWars

Repository files navigation

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 solutionempty-solution

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

  • Select Web API templateaspnet-core-web-api

  • Update all NuGet packagesupdate-all-nuget-packages

  • Update project.json with correct runtime

"runtimes": {"win10-x64": { }}
  • Install GraphQL NuGet packageinstall-graphql-nuget-package

  • Create 'StarWars.Core' projectstarwars-core-project

  • Create 'Droid' model

namespaceStarWars.Core.Models{publicclassDroid{publicintId{get;set;}publicstringName{get;set;}}}
  • Create 'DroidType' model
usingGraphQL.Types;usingStarWars.Core.Models;namespaceStarWars.Api.Models{publicclassDroidType:ObjectGraphType<Droid>{publicDroidType(){Field(x=>x.Id).Description("The Id of the Droid.");Field(x=>x.Name,nullable:true).Description("The name of the Droid.");}}}
  • Create 'StarWarsQuery' model
usingGraphQL.Types;usingStarWars.Core.Models;namespaceStarWars.Api.Models{publicclassStarWarsQuery:ObjectGraphType{publicStarWarsQuery(){Field<DroidType>("hero",resolve: context=>newDroid{Id=1,Name="R2-D2"});}}}
  • Create 'GraphQLQuery' model
namespaceStarWars.Api.Models{publicclassGraphQLQuery{publicstringOperationName{get;set;}publicstringNamedQuery{get;set;}publicstringQuery{get;set;}publicstringVariables{get;set;}}}
  • Create 'GraphQLController'
usingGraphQL;usingGraphQL.Types;usingMicrosoft.AspNetCore.Mvc;usingStarWars.Api.Models;usingSystem.Threading.Tasks;namespaceStarWars.Api.Controllers{[Route("graphql")]publicclassGraphQLController:Controller{[HttpPost]publicasyncTask<IActionResult>Post([FromBody]GraphQLQueryquery){varschema=newSchema{Query=newStarWarsQuery()};varresult=awaitnewDocumentExecuter().ExecuteAsync(_=>{_.Schema=schema;_.Query=query.Query;}).ConfigureAwait(false);if(result.Errors?.Count>0){returnBadRequest();}returnOk(result);}}}
  • Test using Postmanpostman-test-query

  • Create 'IDroidRepository' interface

usingStarWars.Core.Models;usingSystem.Threading.Tasks;namespaceStarWars.Core.Data{publicinterfaceIDroidRepository{Task<Droid>Get(intid);}}
  • Create 'StarWars.Data' projectstarwars-data-project

  • Create in memory 'DroidRepository'

usingStarWars.Core.Data;usingSystem.Collections.Generic;usingSystem.Threading.Tasks;usingStarWars.Core.Models;usingSystem.Linq;namespaceStarWars.Data.InMemory{publicclassDroidRepository:IDroidRepository{privateList<Droid>_droids=newList<Droid>{newDroid{Id=1,Name="R2-D2"}};publicTask<Droid>Get(intid){returnTask.FromResult(_droids.FirstOrDefault(droid=>droid.Id==id));}}}
  • Use 'IDroidRepository' in StarWarsQuery
usingGraphQL.Types;usingStarWars.Core.Data;namespaceStarWars.Api.Models{publicclassStarWarsQuery:ObjectGraphType{privateIDroidRepository_droidRepository{get;set;}publicStarWarsQuery(IDroidRepository_droidRepository){Field<DroidType>("hero",resolve: context=>_droidRepository.Get(1));}}}
  • Update creation of StarWarsQuery in GraphQLController
// ...publicasyncTask<IActionResult>Post([FromBody]GraphQLQueryquery){varschema=newSchema{Query=newStarWarsQuery(newDroidRepository())};// ...
  • Test using Postman

  • Configure dependency injection in Startup.cs

// ...publicvoidConfigureServices(IServiceCollectionservices){services.AddMvc();services.AddTransient<StarWarsQuery>();services.AddTransient<IDroidRepository,DroidRepository>();}// ...
  • Use constructor injection of StarWarsQuery in GraphQLController
// ...publicclassGraphQLController:Controller{privateStarWarsQuery_starWarsQuery{get;set;}publicGraphQLController(StarWarsQuerystarWarsQuery){_starWarsQuery=starWarsQuery;}[HttpPost]publicasyncTask<IActionResult>Post([FromBody]GraphQLQueryquery){varschema=newSchema{Query=_starWarsQuery};// ...
  • Add Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.SqlServer Nuget packages to StarWars.Data projectentity-framework-core-nugetentity-framework-sql-server-provider-nuget

  • Create StarWarsContext

usingMicrosoft.EntityFrameworkCore;usingStarWars.Core.Models;namespaceStarWars.Data.EntityFramework{publicclassStarWarsContext:DbContext{publicStarWarsContext(DbContextOptionsoptions):base(options){Database.EnsureCreated();}publicDbSet<Droid>Droids{get;set;}}}
  • Update 'appsetting.json' with database connection
{"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;"  }}
  • Create EF droid repository
usingStarWars.Core.Data;usingSystem.Threading.Tasks;usingStarWars.Core.Models;usingMicrosoft.EntityFrameworkCore;namespaceStarWars.Data.EntityFramework.Repositories{publicclassDroidRepository:IDroidRepository{privateStarWarsContext_db{get;set;}publicDroidRepository(StarWarsContextdb){_db=db;}publicTask<Droid>Get(intid){return_db.Droids.FirstOrDefaultAsync(droid=>droid.Id==id);}}}
  • Create seed data as an extension to StarWarsContext
usingStarWars.Core.Models;usingSystem.Linq;namespaceStarWars.Data.EntityFramework.Seed{publicstaticclassStarWarsSeedData{publicstaticvoidEnsureSeedData(thisStarWarsContextdb){if(!db.Droids.Any()){vardroid=newDroid{Name="R2-D2"};db.Droids.Add(droid);db.SaveChanges();}}}}
  • Configure dependency injection and run data seed in Startup.cs
// ...publicvoidConfigureServices(IServiceCollectionservices){services.AddMvc();services.AddTransient<StarWarsQuery>();services.AddTransient<IDroidRepository,DroidRepository>();services.AddDbContext<StarWarsContext>(options=>options.UseSqlServer(Configuration["ConnectionStrings:StarWarsDatabaseConnection"]));}publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv,ILoggerFactoryloggerFactory,StarWarsContextdb){loggerFactory.AddConsole(Configuration.GetSection("Logging"));loggerFactory.AddDebug();app.UseMvc();db.EnsureSeedData();}// ...
  • Run application and make sure database is createdssms-starwars-database-created

  • Final test using Postmanpostman-test-query

Entity Framework Migrations

  • Add 'Microsoft.EntityFrameworkCore.Design' NuGet package to 'StarWars.Data' projectef-design-nuget

  • Add 'Microsoft.EntityFrameworkCore.Tools.DotNet' NuGet package to 'StarWars.Data' projectef-tools-dotnet-nuget

  • Add tools section in project.json (StarWars.Data)

"tools": {"Microsoft.EntityFrameworkCore.Tools.DotNet":"1.1.0-preview4-final"}
  • Add official workaround for problems with targeting class library (Modify your class library to be a startup application)
    • Add main entry point
    namespaceStarWars.Data.EntityFramework.Workaround{// WORKAROUND: https://docs.efproject.net/en/latest/miscellaneous/cli/dotnet.html#targeting-class-library-projects-is-not-supportedpublicstaticclassProgram{publicstaticvoidMain(){}}}
    • Add build option in project.json
    "buildOptions": {"emitEntryPoint":true}
  • Run migrations command from the console
dotnet ef migrations add Inital -o .\EntityFramework\Migrations

dotnet-ef-migrations

GrahpiQL

  • Add NPM configuration file 'package.json' to StarWars.Api projectnpm-configuration-file

  • Add GraphiQL dependencies and webpack bundle task

{"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"  }}
  • Add webpack configuration 'webpack.config.js'
varwebpack=require('webpack');varExtractTextPlugin=require('extract-text-webpack-plugin');varoutput='./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:[newExtractTextPlugin('style.css',{allChunks:true})]};
  • Install 'NPM Task Runner' extensionnpm-task-runner

  • Configure 'After Build' step in 'Task Runner Explorer'after-build-task-runner-explorer

"-vs-binding": {"AfterBuild": ["start" ] }
  • Add 'Get' action to GraphQL controller and GraphiQL view (~/Views/GraphQL/index.cshtml)
// ...publicclassGraphQLController:Controller{// ...[HttpGet]publicIActionResultIndex(){returnView();}// ...}
<!DOCTYPE html><html><head><metacharset="utf-8"/><metaname="viewport"content="width=device-width"/><title>GraphiQL</title><linkrel="stylesheet"href="~/style.css"/></head><body><divid="app"></div><scriptsrc="~/bundle.js"type="text/javascript"></script></body></html>
  • Add GraphiQL scripts and styles (app.js and app.css to ~/GraphiQL)

    • app.js
    importReactfrom'react';importReactDOMfrom'react-dom';importGraphiQLfrom'graphiql';importfetchfrom'isomorphic-fetch';import'graphiql/graphiql.css';import'./app.css';functiongraphQLFetcher(graphQLParams){returnfetch(window.location.origin+'/graphql',{method:'post',headers:{'Content-Type':'application/json'},body:JSON.stringify(graphQLParams)}).then(response=>response.json());}ReactDOM.render(<GraphiQLfetcher={graphQLFetcher}/>,document.getElementById('app'));
    • app.css
    html,body {height:100%;margin:0;overflow: hidden;width:100%;}#app {height:100vh;}
  • Add static files support

    • Add 'Microsoft.AspNetCore.StaticFiles' NuGetstatic-files-nuget

    • Update configuration in 'Startup.cs'

    // ...publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv,ILoggerFactoryloggerFactory,StarWarsContextdb){loggerFactory.AddConsole(Configuration.GetSection("Logging"));loggerFactory.AddDebug();app.UseStaticFiles();app.UseMvc();db.EnsureSeedData();}// ...
  • Build project and check if bundles were created by webpack under ~/wwwrootbundles-created-by-webpack

  • Run project and enjoy GraphiQLgraphiql

Unit Tests

  • Create 'Class Library (.NET Core)' type 'StarWars.Tests.Unit' projectunit-tests-project

  • Install 'xunit' NuGet package in StarWars.Tests.Unit projectxunit-nuget

  • Install 'dotnet-test-xunit' NuGet package in StarWars.Tests.Unit projectdotnet-test-xunit-nuget

  • Make changes to project.json

    • Set 'testRunner'
    • Reference 'StarWars.Data' project
    • Set 'runtimes'
{"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": {}  }}
  • Create first test for in memory droid repository
usingStarWars.Data.InMemory;usingXunit;namespaceStarWars.Tests.Unit.Data.InMemory{publicclassDroidRepositoryShould{privatereadonlyDroidRepository_droidRepository;publicDroidRepositoryShould(){// Given_droidRepository=newDroidRepository();}[Fact]publicasyncvoidReturnR2D2DroidGivenIdOf1(){// Whenvardroid=await_droidRepository.Get(1);// ThenAssert.NotNull(droid);Assert.Equal("WRONG_NAME",droid.Name);}}}
  • Build and make sure that test is discovered by 'Test Explorer'test-explorer-first-unit-test

  • Run test - it should fail (we want to make sure that we are testing the right thing)first-unit-test-fail

  • Fix test

// ...[Fact]publicasyncvoidReturnR2D2DroidGivenIdOf1(){// Whenvardroid=await_droidRepository.Get(1);// ThenAssert.NotNull(droid);Assert.Equal("R2-D2",droid.Name);}// ...
  • Run test again - it should passfirst-unit-test-pass

  • Install 'Moq' NuGet packagemoq-nuget

  • Install 'Microsoft.EntityFrameworkCore.InMemory' NuGet packageef-in-memory-nuget

  • Add reference to 'StarWars.Core' in project.json

{"dependencies": {"StarWars.Core": {"target":"project"    }  }}
  • Create EF droid repository unit test
usingMicrosoft.EntityFrameworkCore;usingStarWars.Core.Models;usingStarWars.Data.EntityFramework;usingStarWars.Data.EntityFramework.Repositories;usingXunit;namespaceStarWars.Tests.Unit.Data.EntityFramework.Repositories{publicclassDroidRepositoryShould{privatereadonlyDroidRepository_droidRepository;publicDroidRepositoryShould(){// Given// https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/in-memoryvaroptions=newDbContextOptionsBuilder<StarWarsContext>().UseInMemoryDatabase(databaseName:"StarWars").Options;using(varcontext=newStarWarsContext(options)){context.Droids.Add(newDroid{Id=1,Name="R2-D2"});context.SaveChanges();}varstarWarsContext=newStarWarsContext(options);_droidRepository=newDroidRepository(starWarsContext);}[Fact]publicasyncvoidReturnR2D2DroidGivenIdOf1(){// Whenvardroid=await_droidRepository.Get(1);// ThenAssert.NotNull(droid);Assert.Equal("R2-D2",droid.Name);}}}
  • Create GraphQLController unit test
    • First refactor controller to be more testable by using constructor injection
    usingGraphQL;usingGraphQL.Types;usingMicrosoft.AspNetCore.Mvc;usingStarWars.Api.Models;usingSystem.Threading.Tasks;namespaceStarWars.Api.Controllers{[Route("graphql")]publicclassGraphQLController:Controller{privateIDocumentExecuter_documentExecuter{get;set;}privateISchema_schema{get;set;}publicGraphQLController(IDocumentExecuterdocumentExecuter,ISchemaschema){_documentExecuter=documentExecuter;_schema=schema;}[HttpGet]publicIActionResultIndex(){returnView();}[HttpPost]publicasyncTask<IActionResult>Post([FromBody]GraphQLQueryquery){varexecutionOptions=newExecutionOptions{Schema=_schema,Query=query.Query};varresult=await_documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);if(result.Errors?.Count>0){returnBadRequest(result.Errors);}returnOk(result);}}}
    • Configure dependency injection in 'Startup.cs'
    // ...publicvoidConfigureServices(IServiceCollectionservices){// ...services.AddTransient<IDocumentExecuter,DocumentExecuter>();varsp=services.BuildServiceProvider();services.AddTransient<ISchema>(_=>newSchema{Query=sp.GetService<StarWarsQuery>()});}// ...
    • Create test for 'Index' and 'Post' actions
    usingGraphQL;usingGraphQL.Types;usingMicrosoft.AspNetCore.Mvc;usingMoq;usingStarWars.Api.Controllers;usingStarWars.Api.Models;usingSystem.Threading.Tasks;usingXunit;namespaceStarWars.Tests.Unit.Api.Controllers{publicclassGraphQLControllerShould{privateGraphQLController_graphqlController{get;set;}publicGraphQLControllerShould(){// GivenvardocumentExecutor=newMock<IDocumentExecuter>();documentExecutor.Setup(x=>x.ExecuteAsync(It.IsAny<ExecutionOptions>())).Returns(Task.FromResult(newExecutionResult()));varschema=newMock<ISchema>();_graphqlController=newGraphQLController(documentExecutor.Object,schema.Object);}[Fact]publicvoidReturnNotNullViewResult(){// Whenvarresult=_graphqlController.Index()asViewResult;// ThenAssert.NotNull(result);Assert.IsType<ViewResult>(result);}[Fact]publicasyncvoidReturnNotNullExecutionResult(){// Givenvarquery=newGraphQLQuery{Query=@"{ ""query"": ""query { hero { id name } }"""};// Whenvarresult=await_graphqlController.Post(query);// ThenAssert.NotNull(result);varokObjectResult=Assert.IsType<OkObjectResult>(result);varexecutionResult=okObjectResult.Value;Assert.NotNull(executionResult);}}}

Visual Studio 2017 RC upgrade

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

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

{"dependencies": {// remove this:"StarWars.Data": {"target":"project"    },"StarWars.Core": {"target":"project"    }// ...  }}
  • 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' projectintegration-tests-project

  • Change target framework from 'netcoreapp1.0' to 'netcoreapp1.1'

<ProjectSdk="Microsoft.NET.Sdk">  <PropertyGroup>        <TargetFramework>netcoreapp1.1</TargetFramework>  </PropertyGroup><!--...--></Project>
  • Install 'Microsoft.AspNetCore.TestHost' NuGet packagetest-host-nuget

  • Use EF in memory database for 'Test' evironment

    • Install 'Microsoft.EntityFrameworkCore.InMemory' NuGet packageef-in-memory-nuget-api-project

    • Configure it in 'Startup.cs'

    // ...privateIHostingEnvironmentEnv{get;set;}publicclassStartup{// ...Env=env;}publicvoidConfigureServices(IServiceCollectionservices){// ...if(Env.IsEnvironment("Test")){services.AddDbContext<StarWarsContext>(options=>options.UseInMemoryDatabase(databaseName:"StarWars"));}else{services.AddDbContext<StarWarsContext>(options=>options.UseSqlServer(Configuration["ConnectionStrings:StarWarsDatabaseConnection"]));}// ...}// ...
  • Create integration test for GraphQL query (POST)

usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.AspNetCore.TestHost;usingStarWars.Api;usingSystem.Net.Http;usingSystem.Text;usingXunit;namespaceStarWars.Tests.Integration.Api.Controllers{publicclassGraphQLControllerShould{privatereadonlyTestServer_server;privatereadonlyHttpClient_client;publicGraphQLControllerShould(){_server=newTestServer(newWebHostBuilder().UseEnvironment("Test").UseStartup<Startup>());_client=_server.CreateClient();}[Fact]publicasyncvoidReturnR2D2Droid(){// Givenvarquery=@"{                ""query"": ""query { hero { id name } }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.Contains("R2-D2",responseString);}}}

Logs

  • Make sure that logger is configured in Startup.cs
publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv,ILoggerFactoryloggerFactory,StarWarsContextdb){loggerFactory.AddConsole(Configuration.GetSection("Logging"));loggerFactory.AddDebug();// ...}
  • Override ToString method of GraphQLQuery class
publicoverridestringToString(){varbuilder=newStringBuilder();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}");}returnbuilder.ToString();}
  • Add logger to GraphQLController
publicclassGraphQLController:Controller{// ...privatereadonlyILogger_logger;publicGraphQLController(IDocumentExecuterdocumentExecuter,ISchemaschema,ILogger<GraphQLController>logger){// ..._logger=logger;}[HttpGet]publicIActionResultIndex(){_logger.LogInformation("Got request for GraphiQL. Sending GUI back");returnView();}[HttpPost]publicasyncTask<IActionResult>Post([FromBody]GraphQLQueryquery){// ...if(result.Errors?.Count>0){_logger.LogError("GraphQL errors: {0}",result.Errors);returnBadRequest(result);}_logger.LogDebug("GraphQL execution result: {result}",JsonConvert.SerializeObject(result.Data));returnOk(result);}}
  • Add logger to DroidRepository
namespaceStarWars.Data.EntityFramework.Repositories{publicclassDroidRepository:IDroidRepository{privateStarWarsContext_db{get;set;}privatereadonlyILogger_logger;publicDroidRepository(StarWarsContextdb,ILogger<DroidRepository>logger){_db=db;_logger=logger;}publicTask<Droid>Get(intid){_logger.LogInformation("Get droid with id = {id}",id);return_db.Droids.FirstOrDefaultAsync(droid=>droid.Id==id);}}}
  • Add logger to StarWarsContext
namespaceStarWars.Data.EntityFramework{publicclassStarWarsContext:DbContext{publicreadonlyILogger_logger;publicStarWarsContext(DbContextOptionsoptions,ILogger<StarWarsContext>logger):base(options){_logger=logger;// ...}}}
  • Add logger to StarWarsSeedData
namespaceStarWars.Data.EntityFramework.Seed{publicstaticclassStarWarsSeedData{publicstaticvoidEnsureSeedData(thisStarWarsContextdb){db._logger.LogInformation("Seeding database");if(!db.Droids.Any()){db._logger.LogInformation("Seeding droids");// ...}}}}
  • Fix controller unit test
publicclassGraphQLControllerShould{publicGraphQLControllerShould(){// ...varlogger=newMock<ILogger<GraphQLController>>();_graphqlController=newGraphQLController(documentExecutor.Object,schema.Object,logger.Object);}// ...}
  • Fix repository unit test
usingMicrosoft.EntityFrameworkCore;usingMicrosoft.Extensions.Logging;usingMoq;usingStarWars.Core.Models;usingStarWars.Data.EntityFramework;usingStarWars.Data.EntityFramework.Repositories;usingXunit;namespaceStarWars.Tests.Unit.Data.EntityFramework.Repositories{publicclassDroidRepositoryShould{publicDroidRepositoryShould(){vardbLogger=newMock<ILogger<StarWarsContext>>();// ...using(varcontext=newStarWarsContext(options,dbLogger.Object)){// ...}// ...varrepoLogger=newMock<ILogger<DroidRepository>>();_droidRepository=newDroidRepository(starWarsContext,repoLogger.Object);}}}
  • Enjoy console logsconsole-logs-1console-logs-2

Code Coverage

  • Install OpenCover NuGet packageopen-cover-nuget

  • 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\
  • 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.
<ProjectSdk="Microsoft.NET.Sdk.Web">  <PropertyGroup><!--...-->    <DebugType>Full</DebugType>  </PropertyGroup><!--...--></Project>
  • Run OpenCover in the console
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

  • Install 'ReportGenerator' NuGet packagereport-generator-nuget

  • Create simple script (unit-tests.bat)

mkdir coverage\unitOpenCover.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:userReportGenerator.exe -reports:coverage/unit/coverage.xml -targetdir:coverage/unit -verbosity:Errorstart .\coverage\unit\index.htm
  • Enjoy HTML based code coverage reportopen-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? #3311vsts-new-build-definitionvsts-new-build-definition-github-hosted-vs2017

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

  • Setup repositoryvsts-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

**/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-buildvsts-build-succeeded

  • Enable build badge (after save you will see link to build status image).vsts-enable-badge

Advanced

Full 'Star Wars' database (seeFacebook GraphQL andGraphQL.js)

  • Create models
namespaceStarWars.Core.Models{publicclassEpisode{publicintId{get;set;}publicstringTitle{get;set;}publicvirtualICollection<CharacterEpisode>CharacterEpisodes{get;set;}}}
namespaceStarWars.Core.Models{publicclassPlanet{publicintId{get;set;}publicstringName{get;set;}publicICollection<Human>Humans{get;set;}}}
namespaceStarWars.Core.Models{publicclassCharacter{publicintId{get;set;}publicstringName{get;set;}publicvirtualICollection<CharacterEpisode>CharacterEpisodes{get;set;}publicvirtualICollection<CharacterFriend>CharacterFriends{get;set;}publicvirtualICollection<CharacterFriend>FriendCharacters{get;set;}}}
namespaceStarWars.Core.Models{publicclassCharacterEpisode{publicintCharacterId{get;set;}publicCharacterCharacter{get;set;}publicintEpisodeId{get;set;}publicEpisodeEpisode{get;set;}}}
namespaceStarWars.Core.Models{publicclassCharacterFriend{publicintCharacterId{get;set;}publicCharacterCharacter{get;set;}publicintFriendId{get;set;}publicCharacterFriend{get;set;}}}
namespaceStarWars.Core.Models{publicclassDroid:Character{publicstringPrimaryFunction{get;set;}}}
namespaceStarWars.Core.Models{publicclassHuman:Character{publicPlanetHomePlanet{get;set;}}}
  • Update StarWarsContext
namespaceStarWars.Data.EntityFramework{publicclassStarWarsContext:DbContext{// ...protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){// https://docs.microsoft.com/en-us/ef/core/modeling/relationships// http://stackoverflow.com/questions/38520695/multiple-relationships-to-the-same-table-in-ef7core// episodesmodelBuilder.Entity<Episode>().HasKey(c=>c.Id);modelBuilder.Entity<Episode>().Property(e=>e.Id).ValueGeneratedNever();// planetsmodelBuilder.Entity<Planet>().HasKey(c=>c.Id);modelBuilder.Entity<Planet>().Property(e=>e.Id).ValueGeneratedNever();// charactersmodelBuilder.Entity<Character>().HasKey(c=>c.Id);modelBuilder.Entity<Character>().Property(e=>e.Id).ValueGeneratedNever();// characters-friendsmodelBuilder.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-episodesmodelBuilder.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);// humansmodelBuilder.Entity<Human>().HasOne(h=>h.HomePlanet).WithMany(p=>p.Humans);}publicvirtualDbSet<Episode>Episodes{get;set;}publicvirtualDbSet<Planet>Planets{get;set;}publicvirtualDbSet<Character>Characters{get;set;}publicvirtualDbSet<CharacterFriend>CharacterFriends{get;set;}publicvirtualDbSet<CharacterEpisode>CharacterEpisodes{get;set;}publicvirtualDbSet<Droid>Droids{get;set;}publicvirtualDbSet<Human>Humans{get;set;}}}
  • Update database seed data
namespaceStarWars.Data.EntityFramework.Seed{publicstaticclassStarWarsSeedData{publicstaticvoidEnsureSeedData(thisStarWarsContextdb){db._logger.LogInformation("Seeding database");// episodesvarnewhope=newEpisode{Id=4,Title="NEWHOPE"};varempire=newEpisode{Id=5,Title="EMPIRE"};varjedi=newEpisode{Id=6,Title="JEDI"};varepisodes=newList<Episode>{newhope,empire,jedi,};if(!db.Episodes.Any()){db._logger.LogInformation("Seeding episodes");db.Episodes.AddRange(episodes);db.SaveChanges();}// planetsvartatooine=newPlanet{Id=1,Name="Tatooine"};varalderaan=newPlanet{Id=2,Name="Alderaan"};varplanets=newList<Planet>{tatooine,alderaan};if(!db.Planets.Any()){db._logger.LogInformation("Seeding planets");db.Planets.AddRange(planets);db.SaveChanges();}// humansvarluke=newHuman{Id=1000,Name="Luke Skywalker",CharacterEpisodes=newList<CharacterEpisode>{newCharacterEpisode{Episode=newhope},newCharacterEpisode{Episode=empire},newCharacterEpisode{Episode=jedi}},HomePlanet=tatooine};varvader=newHuman{Id=1001,Name="Darth Vader",CharacterEpisodes=newList<CharacterEpisode>{newCharacterEpisode{Episode=newhope},newCharacterEpisode{Episode=empire},newCharacterEpisode{Episode=jedi}},HomePlanet=tatooine};varhan=newHuman{Id=1002,Name="Han Solo",CharacterEpisodes=newList<CharacterEpisode>{newCharacterEpisode{Episode=newhope},newCharacterEpisode{Episode=empire},newCharacterEpisode{Episode=jedi}},HomePlanet=tatooine};varleia=newHuman{Id=1003,Name="Leia Organa",CharacterEpisodes=newList<CharacterEpisode>{newCharacterEpisode{Episode=newhope},newCharacterEpisode{Episode=empire},newCharacterEpisode{Episode=jedi}},HomePlanet=alderaan};vartarkin=newHuman{Id=1004,Name="Wilhuff Tarkin",CharacterEpisodes=newList<CharacterEpisode>{newCharacterEpisode{Episode=newhope}},};varhumans=newList<Human>{luke,vader,han,leia,tarkin};if(!db.Humans.Any()){db._logger.LogInformation("Seeding humans");db.Humans.AddRange(humans);db.SaveChanges();}// droidsvarthreepio=newDroid{Id=2000,Name="C-3PO",CharacterEpisodes=newList<CharacterEpisode>{newCharacterEpisode{Episode=newhope},newCharacterEpisode{Episode=empire},newCharacterEpisode{Episode=jedi}},PrimaryFunction="Protocol"};varartoo=newDroid{Id=2001,Name="R2-D2",CharacterEpisodes=newList<CharacterEpisode>{newCharacterEpisode{Episode=newhope},newCharacterEpisode{Episode=empire},newCharacterEpisode{Episode=jedi}},PrimaryFunction="Astromech"};vardroids=newList<Droid>{threepio,artoo};if(!db.Droids.Any()){db._logger.LogInformation("Seeding droids");db.Droids.AddRange(droids);db.SaveChanges();}// update character's friendsluke.CharacterFriends=newList<CharacterFriend>{newCharacterFriend{Friend=han},newCharacterFriend{Friend=leia},newCharacterFriend{Friend=threepio},newCharacterFriend{Friend=artoo}};vader.CharacterFriends=newList<CharacterFriend>{newCharacterFriend{Friend=tarkin}};han.CharacterFriends=newList<CharacterFriend>{newCharacterFriend{Friend=luke},newCharacterFriend{Friend=leia},newCharacterFriend{Friend=artoo}};leia.CharacterFriends=newList<CharacterFriend>{newCharacterFriend{Friend=luke},newCharacterFriend{Friend=han},newCharacterFriend{Friend=threepio},newCharacterFriend{Friend=artoo}};tarkin.CharacterFriends=newList<CharacterFriend>{newCharacterFriend{Friend=vader}};threepio.CharacterFriends=newList<CharacterFriend>{newCharacterFriend{Friend=luke},newCharacterFriend{Friend=han},newCharacterFriend{Friend=leia},newCharacterFriend{Friend=artoo}};artoo.CharacterFriends=newList<CharacterFriend>{newCharacterFriend{Friend=luke},newCharacterFriend{Friend=han},newCharacterFriend{Friend=leia}};varcharacters=newList<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();}}}}
  • Add 'Microsoft.EntityFrameworkCore.Tools' NuGetef-tools-nuget

  • Set 'StarWars.Data' as a StartUp project

  • Add 'Full' migrationsef-full-migration

  • Update databaseef-update-database-fullef-update-database-full-ssms

  • Set 'StarWars.Api' as a StartUp project

  • Run 'StarWars.Api' to seed databaseef-seed-full-databaseseeded-full-database-smss

  • Create integration test checking EF configuration and seeded data

namespaceStarWars.Tests.Integration.Data.EntityFramework{publicclassStarWarsContextShould{[Fact]publicasyncvoidReturnR2D2Droid(){// Givenusing(vardb=newStarWarsContext()){// Whenvarr2d2=awaitdb.Droids.Include("CharacterEpisodes.Episode").Include("CharacterFriends.Friend").FirstOrDefaultAsync(d=>d.Id==2001);// ThenAssert.NotNull(r2d2);Assert.Equal("R2-D2",r2d2.Name);Assert.Equal("Astromech",r2d2.PrimaryFunction);varepisodes=r2d2.CharacterEpisodes.Select(e=>e.Episode.Title);Assert.Equal(newstring[]{"NEWHOPE","EMPIRE","JEDI"},episodes);varfriends=r2d2.CharacterFriends.Select(e=>e.Friend.Name);Assert.Equal(newstring[]{"Luke Skywalker","Han Solo","Leia Organa"},friends);}}}}
  • Make sure all tests passall-tests-pass-full-database

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

namespaceStarWars.Api.Models{publicclassStarWarsQuery:ObjectGraphType{// ...publicStarWarsQuery(IDroidRepository_droidRepository){Field<DroidType>("hero",resolve: context=>_droidRepository.Get(2001));}}}
  • Make sure application still worksgraphiql-full-database

Base/generic repository

  • Create generic entity interface
namespaceStarWars.Core.Data{publicinterfaceIEntity<TKey>{TKeyId{get;set;}}}
  • Update models to inherit from IEntity interface (integer based id)
namespaceStarWars.Core.Models{publicclassCharacter:IEntity<int>{// ...}}
namespaceStarWars.Core.Models{publicclassEpisode:IEntity<int>{// ...}}
namespaceStarWars.Core.Models{publicclassPlanet:IEntity<int>{// ...}}
  • Create base/generic repository interface
namespaceStarWars.Core.Data{publicinterfaceIBaseRepository<TEntity,inTKey>whereTEntity:class{Task<List<TEntity>>GetAll();Task<TEntity>Get(TKeyid);TEntityAdd(TEntityentity);voidAddRange(IEnumerable<TEntity>entities);voidDelete(TKeyid);voidUpdate(TEntityentity);Task<bool>SaveChangesAsync();}}
  • Create Entity Framework base/generic repository
namespaceStarWars.Data.EntityFramework.Repositories{publicabstractclassBaseRepository<TEntity,TKey>:IBaseRepository<TEntity,TKey>whereTEntity:class,IEntity<TKey>,new(){protectedDbContext_db;protectedreadonlyILogger_logger;protectedBaseRepository(){}protectedBaseRepository(DbContextdb,ILoggerlogger){_db=db;_logger=logger;}publicvirtualTask<List<TEntity>>GetAll(){return_db.Set<TEntity>().ToListAsync();}publicvirtualTask<TEntity>Get(TKeyid){_logger.LogInformation("Get {type} with id = {id}",typeof(TEntity).Name,id);return_db.Set<TEntity>().SingleOrDefaultAsync(c=>c.Id.Equals(id));}publicvirtualTEntityAdd(TEntityentity){_db.Set<TEntity>().Add(entity);returnentity;}publicvoidAddRange(IEnumerable<TEntity>entities){_db.Set<TEntity>().AddRange(entities);}publicvirtualvoidDelete(TKeyid){varentity=newTEntity{Id=id};_db.Set<TEntity>().Attach(entity);_db.Set<TEntity>().Remove(entity);}publicvirtualasyncTask<bool>SaveChangesAsync(){return(await_db.SaveChangesAsync())>0;}publicvirtualvoidUpdate(TEntityentity){_db.Set<TEntity>().Attach(entity);_db.Entry(entity).State=EntityState.Modified;}}}
  • Refactor EF Droid repository
namespaceStarWars.Core.Data{publicinterfaceIDroidRepository:IBaseRepository<Droid,int>{}}
namespaceStarWars.Data.EntityFramework.Repositories{publicclassDroidRepository:BaseRepository<Droid,int>,IDroidRepository{publicDroidRepository(){}publicDroidRepository(StarWarsContextdb,ILogger<DroidRepository>logger):base(db,logger){}}}
  • Refactor in-memeory Droid repository
namespaceStarWars.Data.InMemory{publicclassDroidRepository:IDroidRepository{privatereadonlyILogger_logger;publicDroidRepository(){}publicDroidRepository(ILogger<DroidRepository>logger){_logger=logger;}privateList<Droid>_droids=newList<Droid>{newDroid{Id=1,Name="R2-D2"}};publicTask<Droid>Get(intid){_logger.LogInformation("Get droid with id = {id}",id);returnTask.FromResult(_droids.FirstOrDefault(droid=>droid.Id==id));}// ...// rest of the methods are not implemented// for now they are just throwing  NotImplementedException}}
  • 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

Install-Package Microsoft.NETCore.App

package-manager-console-netcore-app-upgrade

  • Fix 'DroidType' unit test (capitalization of field names)
namespaceStarWars.Tests.Unit.Api.Models{publicclassDroidTypeShould{[Fact]publicvoidHaveIdAndNameFields(){// WhenvardroidType=newDroidType();// ThenAssert.NotNull(droidType);Assert.True(droidType.HasField("Id"));Assert.True(droidType.HasField("Name"));}}}

Repositories

  • Create rest of the repositories (Character, Episode, Human, Planet)
namespaceStarWars.Core.Data{publicinterfaceIHumanRepository:IBaseRepository<Human,int>{}}
namespaceStarWars.Data.EntityFramework.Repositories{publicclassHumanRepository:BaseRepository<Human,int>,IHumanRepository{publicHumanRepository(){}publicHumanRepository(StarWarsContextdb,ILogger<HumanRepository>logger):base(db,logger){}}}
  • Update base repository with 'include' versions
namespaceStarWars.Core.Data{publicinterfaceIBaseRepository<TEntity,inTKey>whereTEntity:class{// ...Task<List<TEntity>>GetAll(stringinclude);Task<List<TEntity>>GetAll(IEnumerable<string>includes);// ...Task<TEntity>Get(TKeyid,stringinclude);Task<TEntity>Get(TKeyid,IEnumerable<string>includes);// ...}}
namespaceStarWars.Data.EntityFramework.Repositories{publicabstractclassBaseRepository<TEntity,TKey>:IBaseRepository<TEntity,TKey>whereTEntity:class,IEntity<TKey>,new(){// ...publicTask<List<TEntity>>GetAll(stringinclude){_logger.LogInformation("Get all {type}s (including {include})",typeof(TEntity).Name,include);return_db.Set<TEntity>().Include(include).ToListAsync();}publicTask<List<TEntity>>GetAll(IEnumerable<string>includes){_logger.LogInformation("Get all {type}s (including [{includes}])",typeof(TEntity).Name,string.Join(",",includes));varquery=_db.Set<TEntity>().AsQueryable();query=includes.Aggregate(query,(current,include)=>current.Include(include));returnquery.ToListAsync();}// ...publicTask<TEntity>Get(TKeyid,stringinclude){_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));}publicTask<TEntity>Get(TKeyid,IEnumerable<string>includes){_logger.LogInformation("Get {type} with id = {id} (including [{include}])",typeof(TEntity).Name,id,string.Join(",",includes));varquery=_db.Set<TEntity>().AsQueryable();query=includes.Aggregate(query,(current,include)=>current.Include(include));returnquery.SingleOrDefaultAsync(c=>c.Id.Equals(id));}// ...}}
  • Create repositories CRUD unit tests
namespaceStarWars.Tests.Unit.Data.EntityFramework.Repositories{publicclassHumanRepositoryShould{privatereadonlyHumanRepository_humanRepository;privateDbContextOptions<StarWarsContext>_options;privateMock<ILogger<StarWarsContext>>_dbLogger;publicHumanRepositoryShould(){// Given_dbLogger=newMock<ILogger<StarWarsContext>>();_options=newDbContextOptionsBuilder<StarWarsContext>().UseInMemoryDatabase(databaseName:"StarWars_HumanRepositoryShould").Options;using(varcontext=newStarWarsContext(_options,_dbLogger.Object)){context.EnsureSeedData();}varstarWarsContext=newStarWarsContext(_options,_dbLogger.Object);varrepoLogger=newMock<ILogger<HumanRepository>>();_humanRepository=newHumanRepository(starWarsContext,repoLogger.Object);}[Fact]publicasyncvoidReturnLukeGivenIdOf1000(){// Whenvarluke=await_humanRepository.Get(1000);// ThenAssert.NotNull(luke);Assert.Equal("Luke Skywalker",luke.Name);}[Fact]publicasyncvoidReturnLukeFriendsAndEpisodes(){// Whenvarcharacter=await_humanRepository.Get(1000,includes:new[]{"CharacterEpisodes.Episode","CharacterFriends.Friend"});// ThenAssert.NotNull(character);Assert.NotNull(character.CharacterEpisodes);varepisodes=character.CharacterEpisodes.Select(e=>e.Episode.Title);Assert.Equal(new[]{"NEWHOPE","EMPIRE","JEDI"},episodes);Assert.NotNull(character.CharacterFriends);varfriends=character.CharacterFriends.Select(e=>e.Friend.Name);Assert.Equal(new[]{"Han Solo","Leia Organa","C-3PO","R2-D2"},friends);}[Fact]publicasyncvoidReturnLukesHomePlanet(){// Whenvarluke=await_humanRepository.Get(1000,include:"HomePlanet");// ThenAssert.NotNull(luke);Assert.NotNull(luke.HomePlanet);Assert.Equal("Tatooine",luke.HomePlanet.Name);}[Fact]publicasyncvoidAddNewHuman(){// Givenvarhuman10101=newHuman{Id=10101,Name="Human10101"};// When_humanRepository.Add(human10101);varsaved=await_humanRepository.SaveChangesAsync();// ThenAssert.True(saved);using(vardb=newStarWarsContext(_options,_dbLogger.Object)){varhuman=awaitdb.Humans.FindAsync(10101);Assert.NotNull(human);Assert.Equal(10101,human.Id);Assert.Equal("Human10101",human.Name);// Cleanupdb.Humans.Remove(human);awaitdb.SaveChangesAsync();}}[Fact]publicasyncvoidUpdateExistingHuman(){// Givenvarvader=await_humanRepository.Get(1001);vader.Name="Human1001";// When_humanRepository.Update(vader);varsaved=await_humanRepository.SaveChangesAsync();// ThenAssert.True(saved);using(vardb=newStarWarsContext(_options,_dbLogger.Object)){varhuman=awaitdb.Humans.FindAsync(1001);Assert.NotNull(human);Assert.Equal(1001,human.Id);Assert.Equal("Human1001",human.Name);// Cleanuphuman.Name="Darth Vader";db.Humans.Update(human);awaitdb.SaveChangesAsync();}}[Fact]publicasyncvoidDeleteExistingHuman(){// Givenusing(vardb=newStarWarsContext(_options,_dbLogger.Object)){varhuman10102=newHuman{Id=10102,Name="Human10102"};awaitdb.Humans.AddAsync(human10102);awaitdb.SaveChangesAsync();}// When_humanRepository.Delete(10102);varsaved=await_humanRepository.SaveChangesAsync();// ThenAssert.True(saved);using(vardb=newStarWarsContext(_options,_dbLogger.Object)){vardeletedHuman=awaitdb.Humans.FindAsync(10101);Assert.Null(deletedHuman);}}}}
  • Check test resultstest-explorer-repositories-crud-testscode-coverage-repositories-crud-tests

GraphQL queries

namespaceStarWars.Tests.Integration.Api.Controllers{publicclassGraphQLControllerShould{// ...[Fact][Trait("test","integration")]publicasyncvoidExecuteHeroNameQuery(){// Givenconststringquery=@"{                ""query"":                    ""query HeroNameQuery {                        hero {                            name                        }                    }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=JObject.Parse(responseString);Assert.NotNull(jobj);Assert.Equal("R2-D2",(string)jobj["data"]["hero"]["name"]);}[Fact][Trait("test","integration")]publicasyncvoidExecuteHeroNameAndFriendsQuery(){// Givenconststringquery=@"{                ""query"":                    ""query HeroNameAndFriendsQuery {                        hero {                            id                            name                            friends {                                id                                name                            }                        }                    }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=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")]publicasyncvoidExecuteNestedQuery(){// Givenconststringquery=@"{                ""query"":                    ""query NestedQuery {                        hero {                            name                            friends {                                name                                appearsIn                                friends {                                    name                                }                            }                        }                    }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=JObject.Parse(responseString);Assert.NotNull(jobj);varluke=jobj["data"]["hero"]["friends"][0];varepisodes=((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")]publicasyncvoidExecuteFetchLukeQuery(){// Givenconststringquery=@"{                ""query"":                    ""query FetchLukeQuery {                        human(id: ""1000"") {                            name                        }                    }                }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=JObject.Parse(responseString);Assert.NotNull(jobj);Assert.Equal("Luke Skywalker",(string)jobj["data"]["human"]["name"]);}[Fact][Trait("test","integration")]publicasyncvoidExecuteFetchLukeAliased(){// Givenconststringquery=@"{                ""query"":                    ""query FetchLukeAliased {                        luke: human(id: ""1000"") {                            name                        }                    }                }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=JObject.Parse(responseString);Assert.NotNull(jobj);Assert.Equal("Luke Skywalker",(string)jobj["data"]["human"]["name"]);}[Fact][Trait("test","integration")]publicasyncvoidExecuteFetchLukeAndLeiaAliased(){// Givenconststringquery=@"{                ""query"":                    ""query FetchLukeAliased {                        luke: human(id: ""1000"") {                            name                        }                        leia: human(id: ""1003"") {                            name                        }                    }                }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=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")]publicasyncvoidExecuteDuplicateFields(){// Givenconststringquery=@"{                ""query"":                    ""query DuplicateFields {                        luke: human(id: ""1000"") {                            name                            homePlanet                        }                        leia: human(id: ""1003"") {                            name                            homePlanet                        }                    }                }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=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")]publicasyncvoidExecuteUseFragment(){// Givenconststringquery=@"{                ""query"":                    ""query UseFragment {                        luke: human(id: ""1000"") {                            ...HumanFragment                        }                        leia: human(id: ""1003"") {                            ...HumanFragment                        }                    }                    fragment HumanFragment on Human {                        name                        homePlanet                    }                }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=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")]publicasyncvoidExecuteCheckTypeOfR2(){// Givenconststringquery=@"{                ""query"":                    ""query CheckTypeOfR2 {                        hero {                            __typename                            name                        }                    }                }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=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")]publicasyncvoidExecuteCheckTypeOfLuke(){// Givenconststringquery=@"{                ""query"":                    ""query CheckTypeOfLuke {                       hero(episode: EMPIRE) {                            __typename                            name                       }                    }                }""            }";varcontent=newStringContent(query,Encoding.UTF8,"application/json");// Whenvarresponse=await_client.PostAsync("/graphql",content);// Thenresponse.EnsureSuccessStatusCode();varresponseString=awaitresponse.Content.ReadAsStringAsync();Assert.NotNull(responseString);varjobj=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

About

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

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp