{"id":25522211,"url":"https://github.com/dotnet-labs/controllerunittests","last_synced_at":"2026-04-27T18:32:29.471Z","repository":{"id":108781018,"uuid":"380110756","full_name":"dotnet-labs/ControllerUnitTests","owner":"dotnet-labs","description":"Unit testing Controllers with ClaimsPrincipal | User | Identity | Claims","archived":false,"fork":false,"pushed_at":"2023-12-08T17:33:34.000Z","size":202,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-15T13:56:31.497Z","etag":null,"topics":["claims","controller","credentials","dotnet","dotnet5","dotnetcore","identity","unit-testing","unittest","user"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dotnet-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":"changhuixu","tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2021-06-25T03:12:56.000Z","updated_at":"2023-12-08T17:33:37.000Z","dependencies_parsed_at":"2024-10-23T23:02:13.553Z","dependency_job_id":"2896584a-ed32-4c34-b5e6-750b1f095b80","html_url":"https://github.com/dotnet-labs/ControllerUnitTests","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dotnet-labs/ControllerUnitTests","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotnet-labs%2FControllerUnitTests","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotnet-labs%2FControllerUnitTests/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotnet-labs%2FControllerUnitTests/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotnet-labs%2FControllerUnitTests/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dotnet-labs","download_url":"https://codeload.github.com/dotnet-labs/ControllerUnitTests/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotnet-labs%2FControllerUnitTests/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32349506,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-27T17:12:42.749Z","status":"ssl_error","status_checked_at":"2026-04-27T17:12:41.658Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claims","controller","credentials","dotnet","dotnet5","dotnetcore","identity","unit-testing","unittest","user"],"created_at":"2025-02-19T18:18:44.667Z","updated_at":"2026-04-27T18:32:29.452Z","avatar_url":"https://github.com/dotnet-labs.png","language":"C#","funding_links":["https://ko-fi.com/changhuixu","https://ko-fi.com/I3I63W4OK"],"categories":[],"sub_categories":[],"readme":"# Controller Unit Tests\n\n[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I3I63W4OK)\n\n![controller tests](./controller-test.png)\n\nWe do different levels of testings against controllers. When unit testing controller logic, the test only executes a single action method without going through the framework's request/response pipeline. In other words, the filter attributes (e.g., the `Authorize` attribute ), model binding, and middlewares do not affect unit tests for controllers. Thus, unit testing controller logic is quite similar to testing a regular class where we can mock dependencies and stage input values and verify outputs.\n\nOne thing is different is that a controller has an implicit dependency, `ControllerContext`, inherited from `ControllerBase`. The `ControllerContext` includes the frequently used `HttpContext`, `ModelState`, and other features.\n\nIn this article, we will go over some use cases when unit testing an action method that involves HttpContext. For example, an action method checks the current user's role based on `ClaimIdentity`, an action method checks HttpRequest header values. We will see how to stage HttpContext and Request. Following a similar fashion, we should also know how to stage Response and ModelState in unit tests.\n\n## Claims\n\nClaims are usually being used to inspect permissions and/or differentiate results. For example, in an action method, we can first get a user's role using `User.FindFirstValue(ClaimTypes.Role)`, then determine the next step based on the role's permission level.\n\nWhen unit testing an action method, we want to check the behaviors for different user roles. In this case, we can configure the controller's ControllerContext and set the HttpContext with a desired user. Let's take a look at the following test.\n\n```csharp\nvar user = new ClaimsPrincipal(new ClaimsIdentity(new[] {\n    new Claim(ClaimTypes.NameIdentifier, \"11234568\"),\n    new Claim(ClaimTypes.Name, \"admin last\"),\n    new Claim(ClaimTypes.Role, \"Admin\")\n}));\nvar controller = new ValuesController(new NullLogger\u003cValuesController\u003e())\n{\n    ControllerContext = { HttpContext = new DefaultHttpContext { User = user } }\n};\n\nvar response = controller.Get();\n\nAssert.IsInstanceOfType(response, typeof(OkObjectResult));\nvar result = ((OkObjectResult)response).Value as string[];\nAssert.IsNotNull(result);\nAssert.AreEqual(2, result.Length);\nAssert.AreEqual(\"value1\", result[0]);\nAssert.AreEqual(\"value2\", result[1]);\n```\n\n## Request Headers\n\nKnowing how to hook up HttpContext to a controller, we can try something out about configuring Http Request.\n\nLet's use a contrived action method as an example. The logic in this action method is based on the language in Request Headers, where the language value can be obtained by using `var lang = Request.Headers[\"lang\"].ToString();`. In order to stage a request header key-value pairs, we can first attach an HttpContext to the controller, then set up the desired request header.\n\n```csharp\nvar controller = new ValuesController(new NullLogger\u003cValuesController\u003e())\n{\n    ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() }\n};\ncontroller.Request.Headers[\"lang\"] = language;\n\nvar response = controller.GetValueById(id);\n\nAssert.IsInstanceOfType(response, typeof(OkObjectResult));\n```\n\n## ModelState\n\nSimilarly, we can manipulate ModelState based on our needs.\n\n```csharp\nvar controller = new ValuesController(new NullLogger\u003cValuesController\u003e());\ncontroller.ModelState.Clear();\ncontroller.ModelState.AddModelError(\"test\", \"test\");\n```\n\n## License\n\nFeel free to use the code in this repository as it is under MIT license.\n\n[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I3I63W4OK)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdotnet-labs%2Fcontrollerunittests","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdotnet-labs%2Fcontrollerunittests","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdotnet-labs%2Fcontrollerunittests/lists"}