Skip to content

Commit 41540d5

Browse files
DarthPedroegil
andauthored
Dev authorization fakes (#151)
* Created fake helper classes to assist with testing Blazor authentication/authorization. Added unit tests for them. * Added extension methods to add appropriate authentication/authorization test service, in the right state, to the TestServiceProvider. Added unit tests. * Added component tests that use AuthorizeView and the BUnit authorization fakes to validate the various states. * Updated changelog and minor cleanup for SimpleAuthView and test. * Added placeholders for AuthenticationStateProvider and IAuthorizationService to give user friendly reminder to call AddAuthorization to TestContext.Services. * committing this to be able to switch * Created fake helper classes to assist with testing Blazor authentication/authorization. Added unit tests for them. * Added extension methods to add appropriate authentication/authorization test service, in the right state, to the TestServiceProvider. Added unit tests. * Added component tests that use AuthorizeView and the BUnit authorization fakes to validate the various states. * Updated changelog and minor cleanup for SimpleAuthView and test. * Added placeholders for AuthenticationStateProvider and IAuthorizationService to give user friendly reminder to call AddAuthorization to TestContext.Services. * Added documentation for fake authorization services and how to use them. * Fixed code formatting in documentation. * Update docs/site/docs/mocking/mocking-auth.md Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web/TestDoubles/Authorization/FakeAuthenticationStateProvider.cs Change to method comment. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update docs/site/docs/mocking/mocking-auth.md changing mocking to faking Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/FakeAuthorizationPolicyProviderTest.cs Removing ConfigureAwait call. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplay.razor removing NavigationManager Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplayTest.cs removed navigation manager from component. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplayTest.cs removed navigation manager from component. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/SimpleAuthView.razor remove unnecessary @code block Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplayTest.cs removed navigation manager from component. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplayTest.cs removed navigation manager from component. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Created fake helper classes to assist with testing Blazor authentication/authorization. Added unit tests for them. * Added extension methods to add appropriate authentication/authorization test service, in the right state, to the TestServiceProvider. Added unit tests. * Added component tests that use AuthorizeView and the BUnit authorization fakes to validate the various states. * Updated changelog and minor cleanup for SimpleAuthView and test. * Added placeholders for AuthenticationStateProvider and IAuthorizationService to give user friendly reminder to call AddAuthorization to TestContext.Services. * Update src/bunit.web/TestDoubles/Authorization/FakeAuthenticationStateProvider.cs Change to method comment. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/FakeAuthorizationPolicyProviderTest.cs Removing ConfigureAwait call. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplay.razor removing NavigationManager Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplayTest.cs removed navigation manager from component. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplayTest.cs removed navigation manager from component. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/SimpleAuthView.razor remove unnecessary @code block Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplayTest.cs removed navigation manager from component. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web.tests/TestDoubles/Authorization/LoginDisplayTest.cs removed navigation manager from component. Co-authored-by: Egil Hansen <egil@assimilated.dk> * Removed this prefixes from usage in code. * Rename SimpleAuthViewTest to AuthorizationTests to better describe what it is doing. Remove LoginDisplay component and tests since they are redundant with SimpleAuthView and its tests. * Added AuthorizationState enum to encapsulate the states. Changed API parameter to use AuthorizationState rather than isAuthorized boolean. * Renamed FakeAuthorizationExtensions.AddAuthorization to AddTestAuthorization * Replaced FakeAuthorizationService.NextResult to be CurrentState with an AuthorizationState enum value. Changed FakeAuthenticationStateProvider.TriggerAuthenticationStateChanged to take userName and optional roles parameters. * Added simplifying methods to FakeAuthorizationExtensions to UpdateTestAuthorizationState. Removes requiring boilerplate state change code in tests. * Added factory method to simplify creating ClaimsPrincipal when needed. * Update docs/site/docs/test-doubles/faking-auth.md Updated doc title Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update docs/site/docs/test-doubles/faking-auth.md updated doc title Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/bunit.web/TestDoubles/Authorization/MissingFakeAuthorizationException.cs Updated exception help link Co-authored-by: Egil Hansen <egil@assimilated.dk> * Fix build failure caused by IList property with a setter. * Moved SimpleAuthTest to bunit.testassets project. Fixed tests to reference new component. * Add an explicit SetAuthorizationState to FakeAuthorizationService. * Hide the CurrentState property because it's an implementation detail. * Made policy scheme name a settable property with a default to TestScheme (in case users need to explicitly set it). * Created an AuthorizationAdaptor to wrap some of the auth services and state. Refactored to simplify FakeAuthorizationExtensions methods. Fixed tests. * Renamed AuthorizationAdaptor to TestAuthorizationContext and minor clean up. * Changed Roles properties to be non-nullable in TestAuthorizationContext and FakePrincipal. * Renamed TestAuthorizationContext methods to SetAuthorized and SetNotAuthorized. * Update CHANGELOG.md with PR comments Co-authored-by: Egil Hansen <egil@assimilated.dk> * Minor clean ups from PR comments. * Added check to property setters in FakeIdentity and FakeAuthorizationPolicyProvider. * Exposed AuthorizeCalls through TestAuthorizationContext for callers to easily access it in tests. * Small style tweaks to code, spelling, etc. * Added back .Render() to test removed during experiments * Additional simplifications of code, update to changelog * Fixed tests and code that broke during refactor * Added empty string check to PolicySchemeName. Removed AuthorizeCalls * Added documentation sample code to the bunit.docs.samples project. Update markdown files to reference sample code rather than duplicate it. * Made roles list in SetAuthorized a params string[] roles, making it easier to pass just one role. Fixed up tests. * Added TestAuthorizationContext.SetAuthorizationPolicies as way for callers to specify policy names when they need to test AuthorizeView with Policy set. Changed FakeAuthorizationPolicy to validate policy name in GetPolicyAsync. * Fix up sample build break. * Added SimpleAuthViewWithPolicy component to validate testing with AuthorizationView.Policy set. * Added sample code and unit tests for using AuthorizeView with Policy. * changed role names, and removed ConfigureAwait calls from test classes. * Made CreatePrincipal internal * Added SetRoles method, and made the TestAuthorizationContext methods chainable. * Updated samples to the TestAuthorizationContext api changes. * Updated docs to match the style of the other pages * Added docs samples * Tests (which breaks) for missing or invalid roles and policies * removed src/bunit.testassets/SampleComponents/SimpleWithJsRuntimeDep.razor * Readded SimpleWithJSRuntimeDep.razor * Fix FakeAuthorizationService to validate checks for policies and roles in AuthorizeAsync method. * Added SetAuthorizing method and state implementation to get AuthorizeView to behave as expected. Updated sample code too. * Added TestAuthorizationContext.SetClaims and implementation in services, new SimpleAuthViewWithClaims component, and unit tests. * Clean up from PR comments. * Added negative test for AuthorizeView without defined claims. * Update docs/samples/components/UserInfo.razor sample white space formtting Co-authored-by: Egil Hansen <egil@assimilated.dk> * Changed code to remove null pointer exception if authorizing state was set before call to RenderComponent. Additional other tweaks. * Docs: added authorizing example to tests, fixed spelling and gramma Co-authored-by: Egil Hansen <egil@assimilated.dk>
1 parent 6b8dc3f commit 41540d5

36 files changed

+1932
-6
lines changed

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,45 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
99
### Added
1010
List of new features.
1111

12+
- Authorization fakes added to make it much easier to test components that use authentication and authorization. By [@DarthPedro](https://github.com/DarthPedro) in [#151](https://github.com/egil/bUnit/pull/151).
13+
14+
#### Authorization Fakes
15+
Added authentication/authorization fake services to make it easy to test Blazor components that use authorization either through code or the AuthorizeView component.
16+
You just need to call the AddTestAuthorization method on your TestContext.Services collection.
17+
18+
First define your component that uses AuthorizeView to render differently based on the user's authorization state:
19+
```
20+
@using Microsoft.AspNetCore.Authorization
21+
@using Microsoft.AspNetCore.Components.Authorization
22+
23+
<CascadingAuthenticationState>
24+
<AuthorizeView>
25+
<Authorized>
26+
Authorized!
27+
</Authorized>
28+
<NotAuthorized>
29+
Not authorized?
30+
</NotAuthorized>
31+
</AuthorizeView>
32+
</CascadingAuthenticationState>
33+
```
34+
Then define your test method to specify the authorization state with a user name, and run your test.
35+
```c#
36+
public void Test002()
37+
{
38+
// arrange
39+
using var ctx = new TestContext();
40+
var authContext = ctx.Services.AddTestAuthorization();
41+
authContext.SetAuthorized("TestUser", AuthorizationState.Authorized);
42+
43+
// act
44+
var cut = ctx.RenderComponent<SimpleAuthView>();
45+
46+
// assert
47+
cut.MarkupMatches("Authorized!");
48+
}
49+
```
50+
1251
### Changed
1352
List of changes in existing functionality.
1453

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@using Microsoft.AspNetCore.Components.Authorization
2+
@inject AuthenticationStateProvider AuthenticationStateProvider
3+
4+
<p>Test Component</p>
5+
6+
@if (isAuthenticated)
7+
{
8+
<p>User: @userName</p>
9+
}
10+
11+
@code{
12+
bool isAuthenticated = false;
13+
string userName;
14+
15+
protected override async Task OnInitializedAsync()
16+
{
17+
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync().ConfigureAwait(false);
18+
if (state != null)
19+
{
20+
this.isAuthenticated = state.User.Identity.IsAuthenticated;
21+
this.userName = state.User.Identity.Name;
22+
}
23+
}
24+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@using Microsoft.AspNetCore.Components.Authorization
2+
@inject AuthenticationStateProvider AuthenticationStateProvider
3+
4+
@if (isAuthenticated)
5+
{
6+
<h1>Welcome @userName</h1>
7+
}
8+
@if (!isAuthenticated)
9+
{
10+
<h1>Please log in!</h1>
11+
}
12+
<CascadingAuthenticationState>
13+
<AuthorizeView>
14+
<Authorized>
15+
<p>State: Authorized</p>
16+
</Authorized>
17+
<Authorizing>
18+
<p>State: Authorizing</p>
19+
</Authorizing>
20+
<NotAuthorized>
21+
<p>State: Not authorized</p>
22+
</NotAuthorized>
23+
</AuthorizeView>
24+
</CascadingAuthenticationState>
25+
26+
@code
27+
{
28+
bool isAuthenticated = false;
29+
string userName;
30+
31+
protected override async Task OnParametersSetAsync()
32+
{
33+
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
34+
isAuthenticated = state.User.Identity.IsAuthenticated;
35+
userName = state.User.Identity.Name;
36+
}
37+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@using Microsoft.AspNetCore.Components.Authorization
2+
@using System.Security.Claims
3+
@using System.Globalization
4+
5+
<CascadingAuthenticationState>
6+
<AuthorizeView>
7+
<h1>Hi @context.User.Identity.Name, you have these claims and rights:</h1>
8+
</AuthorizeView>
9+
<ul>
10+
<AuthorizeView>
11+
@foreach (var claim in @context.User.FindAll(x => x.Type != ClaimTypes.Name))
12+
{
13+
<li>@GetClaimName(claim): @claim.Value</li>
14+
}
15+
</AuthorizeView>
16+
<AuthorizeView Roles="superuser">
17+
<li>You have the role SUPER USER</li>
18+
</AuthorizeView>
19+
<AuthorizeView Roles="admin">
20+
<li>You have the role ADMIN</li>
21+
</AuthorizeView>
22+
<AuthorizeView Policy="content-editor">
23+
<li>You are a CONTENT EDITOR</li>
24+
</AuthorizeView>
25+
</ul>
26+
</CascadingAuthenticationState>
27+
28+
@code
29+
{
30+
private static string GetClaimName(Claim claim)
31+
{
32+
var claimType = new Uri(claim.Type);
33+
var name = claimType.Segments.Last();
34+
return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(name);
35+
}
36+
}

docs/samples/components/bunit.docs.samples.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.1.4" />
12+
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1.4" />
1213
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="3.1.4" />
1314
</ItemGroup>
1415

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Bunit.TestDoubles.Authorization;
2+
using System;
3+
using Xunit;
4+
5+
namespace Bunit.Docs.Samples
6+
{
7+
public class InjectAuthServiceTest
8+
{
9+
[Fact(DisplayName = "Use AuthenticationStateProvider service with authenticated and authorized user")]
10+
public void Test001()
11+
{
12+
// arrange
13+
using var ctx = new TestContext();
14+
var authContext = ctx.Services.AddTestAuthorization();
15+
authContext.SetAuthorized("TestUserName", AuthorizationState.Authorized);
16+
17+
// act
18+
var cut = ctx.RenderComponent<InjectAuthService>();
19+
20+
// assert
21+
Assert.Contains("<p>User: TestUserName</p>", cut.Markup, StringComparison.InvariantCulture);
22+
}
23+
24+
[Fact(DisplayName = "Use AuthenticationStateProvider service with unauthenticated and unauthorized user")]
25+
public void Test002()
26+
{
27+
// arrange
28+
using var ctx = new TestContext();
29+
var authContext = ctx.Services.AddTestAuthorization();
30+
31+
// act
32+
var cut = ctx.RenderComponent<InjectAuthService>();
33+
34+
// assert
35+
Assert.DoesNotContain("User:", cut.Markup, StringComparison.InvariantCulture);
36+
}
37+
}
38+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using Bunit.TestDoubles.Authorization;
2+
using Xunit;
3+
4+
namespace Bunit.Docs.Samples
5+
{
6+
public class UserInfoTest
7+
{
8+
[Fact(DisplayName = "UserInfo with unauthenticated user")]
9+
public void Test001()
10+
{
11+
// Arrange
12+
using var ctx = new TestContext();
13+
ctx.Services.AddTestAuthorization();
14+
15+
// Act
16+
var cut = ctx.RenderComponent<UserInfo>();
17+
18+
// Assert
19+
cut.MarkupMatches(@"<h1>Please log in!</h1>
20+
<p>State: Not authorized</p>");
21+
}
22+
23+
[Fact(DisplayName = "UserInfo while authorizing user")]
24+
public void Test004()
25+
{
26+
// Arrange
27+
using var ctx = new TestContext();
28+
var authContext = ctx.Services.AddTestAuthorization();
29+
authContext.SetAuthorizing();
30+
31+
// Act
32+
var cut = ctx.RenderComponent<UserInfo>();
33+
34+
// Assert
35+
cut.MarkupMatches(@"<h1>Please log in!</h1>
36+
<p>State: Authorizing</p>");
37+
}
38+
39+
[Fact(DisplayName = "UserInfo with authenticated but unauthorized user")]
40+
public void Test002()
41+
{
42+
// Arrange
43+
using var ctx = new TestContext();
44+
var authContext = ctx.Services.AddTestAuthorization();
45+
authContext.SetAuthorized("TEST USER", AuthorizationState.Unauthorized);
46+
47+
// Act
48+
var cut = ctx.RenderComponent<UserInfo>();
49+
50+
// Assert
51+
cut.MarkupMatches(@"<h1>Welcome TEST USER</h1>
52+
<p>State: Not authorized</p>");
53+
}
54+
55+
[Fact(DisplayName = "UserInfo with authenticated and authorized user")]
56+
public void Test003()
57+
{
58+
// Arrange
59+
using var ctx = new TestContext();
60+
var authContext = ctx.Services.AddTestAuthorization();
61+
authContext.SetAuthorized("TEST USER");
62+
63+
// Act
64+
var cut = ctx.RenderComponent<UserInfo>();
65+
66+
// Assert
67+
cut.MarkupMatches(@"<h1>Welcome TEST USER</h1>
68+
<p>State: Authorized</p>");
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)