Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/Build_&_Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ jobs:
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10'
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: 'Run: Test'
run: ./build.cmd Test
3 changes: 3 additions & 0 deletions .github/workflows/Manual_Nuget_Push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10'
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

- name: 'Run: NugetPush'
run: ./build.cmd NugetPush
Expand Down
8 changes: 5 additions & 3 deletions docs/guide/bootstrapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Whilst `AlbaHost.For<T>` is the preferred way of bootstrapping, other options ex

## Initializing AlbaHost with IHostBuilder Directly

To bootstrap a **ASP.NET Core Startup.cs-style** application, create a `AlbaHost` using the definition of your `IHostBuilder` as shown below:
To bootstrap a **ASP.NET Core Startup.cs-style** application, create a `AlbaHost` using the definition of your `IHostBuilder` as shown below.
On .NET 11 this is the way to test a `Startup.cs`-style application, because `AlbaHost.For<T>` only supports applications built
with `WebApplicationBuilder` there.

<!-- snippet: sample_Quickstart3 -->
<a id='snippet-sample_Quickstart3'></a>
Expand Down Expand Up @@ -64,6 +66,6 @@ The `AlbaHost` is an extension of the standard .Net Core [IHost](https://docs.mi
While you can always access the underlying `TestServer` through the `IAlbaHost.Server` property, you're mostly going to be using the `Scenario()` method to write Alba "Scenario" tests.

::: tip
If you build a `AlbaHost` with `AlbaHost.ForStartup<T>()`, it will try to guess at the content root path by the name of assembly
that holds the `Startup` class, but you may need to override that yourself.
Every bootstrapping method returns an `AlbaHostBuilder`, so configuration values, service registrations, and extensions can be
chained on before the host is awaited. See [Configuring the Host Fluently](gettingstarted.md#configuring-the-host-fluently).
:::
35 changes: 15 additions & 20 deletions docs/guide/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public interface IAlbaHostBuilder
has a default no-op implementation, so extensions that only need post-start logic can implement
`Start` alone.

When you are initializing an `AlbaHost`, you can pass in an optional array of extensions like this sample from the security stub
testing:
Extensions are applied when the `AlbaHost` is bootstrapped, either as arguments to any of the `AlbaHost.For(...)` methods or
chained onto the returned builder with `WithExtension(...)`. This sample from the security stub testing passes one as an argument:

<!-- snippet: sample_bootstrapping_with_stub_extension -->
<a id='snippet-sample_bootstrapping_with_stub_extension'></a>
Expand All @@ -73,30 +73,25 @@ theHost = await AlbaHost.For<WebAppSecuredWithJwt.Program>(securityStub);
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Security/web_api_authentication_with_stub.cs#L15-L26' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_with_stub_extension' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Configuration Extension
The same kind of extension chained onto the builder:

In some scenarios it may be more convienent to override a configuration value rather than modifying the service configuration. Due to a current [limitation](https://github.com/dotnet/aspnetcore/issues/37680) in the ASP.NET Core test host, overriding your application's configuration values requires a workaround. Alba includes this workaround out of the box via the `ConfigurationOverride` extension.
For example, to override an application's Postgres connection string:

<!-- snippet: sample_configuration_extension -->
<a id='snippet-sample_configuration_extension'></a>
<!-- snippet: sample_bootstrapping_with_stub_scheme_extension -->
<a id='snippet-sample_bootstrapping_with_stub_scheme_extension'></a>
```cs
var configValues = new Dictionary<string, string?>()
{
{ "ConnectionStrings:Postgres", "MyOverriddenValue" }
};
// Stub out an individual scheme
var securityStub = new AuthenticationStub("custom")
.With("foo", "bar")
.With(JwtRegisteredClaimNames.Email, "guy@company.com")
.WithName("jeremy");

var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>(builder =>
{
builder.ConfigureServices(c =>
{
// services config
});
}, ConfigurationOverride.Create(configValues));
await using var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>()
.WithExtension(securityStub);
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/Extensions.cs#L8-L22' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_configuration_extension' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Security/web_api_authentication_with_individual_stub.cs#L21-L32' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_with_stub_scheme_extension' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

See [Configuring the Host Fluently](gettingstarted.md#configuring-the-host-fluently) for the full builder surface.

## Time Travel with TimeProviderOverride

Time-sensitive endpoints — token or session expiry, cache TTLs, scheduling, "valid until" business
Expand Down
41 changes: 39 additions & 2 deletions docs/guide/gettingstarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ your ASP.NET Core system with Alba and start authoring specifications with the `

## Initializing AlbaHost

Alba is compatible with both traditional-style `Startup.cs` projects as well as the new `WebApplicationBuilder` minimal approach. The following instructions work with both models.
Alba supports both traditional `Startup.cs` projects and the `WebApplicationBuilder` minimal hosting approach. On .NET 10, `AlbaHost.For<T>` works with both models. On .NET 11, `AlbaHost.For<T>` requires a `WebApplicationBuilder` application; bootstrap a `Startup.cs`-style application through its `IHostBuilder` instead, as shown in [Alternative Bootstrapping Methods](bootstrapping.md).

As an example, consider this very small ASP.NET Core application utilizing the new [Minimal API](https://docs.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-6.0&tabs=visual-studio) approach:

Expand Down Expand Up @@ -77,7 +77,7 @@ await using var host = await AlbaHost.For<global::Program>(x =>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Acceptance/web_application_factory_usage.cs#L41-L50' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_with_web_application_factory' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The `AlbaHost.For<T>(Action<WebApplicationFactory<T>> configuration)` method uses `WebApplicationFactory` and all its magic static
The `AlbaHost.For<T>(Action<IWebHostBuilder> configuration)` method uses `WebApplicationFactory` and all its magic static
member trickery to intercept and run the implied `Program.Main()` method from the sample application above while also allowing you to customize
the application configuration at testing time. The "T" in this case is only a marker type so that `WebApplicationFactory` can choose the correct
entry assembly for the web application that is being tested by Alba.
Expand All @@ -89,6 +89,43 @@ for more information.
`AlbaHost` is an expensive object to create, so you'll generally want to reuse it across tests. See the relevant guide for [xUnit](xunit.md) or [NUnit](nunit.md)
:::

## Configuring the Host Fluently

Every bootstrapping method returns an `AlbaHostBuilder`. You can
chain the Alba specific parts of the setup onto it as part of the construction. ASP.NET
Core host customization such as service registrations stays in the configuration action passed to `AlbaHost.For<T>`.

<!-- snippet: sample_fluent_configuration -->
<a id='snippet-sample_fluent_configuration'></a>
```cs
await using var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>(x =>
{
// Service registrations and other ASP.NET Core host
// customization belong in this action
x.ConfigureServices(services =>
{
// services.AddSingleton<IExternalService, StubbedExternalService>();
});
})
// Override configuration values. These are visible to the
// application's own startup code
.WithConfiguration("ConnectionStrings:Postgres", "MyOverriddenValue")
// Apply Alba extensions
.WithExtension(new AuthenticationStub().WithName("jeremy"));
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/Extensions.cs#L11-L28' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_fluent_configuration' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Calls are applied in the order they are chained, so an extension chained later overrides what an earlier one
registered.

Configuration values take precedence over the application's own configuration files. On .NET 11, `AlbaHost.For<T>`
applies them to the `WebApplicationBuilder` as soon as it is created, before the application's own startup code runs,
so a value read in `Program.cs` before `builder.Build()` already sees the override. On .NET 10, `AlbaHost.For<T>` passes the values to
the application as host configuration, which requires the application to call `WebApplication.CreateBuilder(args)`.
Host-level keys such as `environment` and `contentRoot` cannot be changed this way, use `UseEnvironment` on the
`Action<IWebHostBuilder>` overload for those.

## Running a Scenario

Once you have a `AlbaHost` object, you're ready to execute *Scenario's* through your system inside of tests.
Expand Down
6 changes: 4 additions & 2 deletions docs/guide/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ At this point, Alba is a value added wrapper around the [ASP.NET Core TestServer

The *scenario* testing in Alba was inspired by the [testing support in the Scala PlayFramework](https://www.playframework.com/documentation/2.8.x/ScalaFunctionalTestingWithSpecs2).

When using Alba, you actually bootstrap your web application in memory using either the "older"
[HostBuilder model](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuilder?view=dotnet-plat-ext-6.0) or the newer [WebApplicationFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1?view=aspnetcore-6.0) model , then pass the root `IHost` of the running application to Alba's `AlbaHost`, which will be the entry point to using Alba in all of your integration tests.
When using Alba, you bootstrap your web application in memory through one of three models: the application's `IHostBuilder`,
a `WebApplicationBuilder`, or [WebApplicationFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1?view=aspnetcore-6.0)
for an application's entry point. Each returns an awaitable `AlbaHostBuilder` that starts the application and hands back Alba's `AlbaHost`,
which will be the entry point to using Alba in all of your integration tests.

`AlbaHost` is shown in the class diagram below:

Expand Down
3 changes: 3 additions & 0 deletions docs/guide/nunit.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class Application
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/NUnitSamples/UnitTest1.cs#L7-L29' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_NUnit_Application' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Any Alba specific setup, such as configuration overrides or extensions, is chained onto `AlbaHost.For<T>()` before it is awaited
(see [Configuring the Host Fluently](gettingstarted.md#configuring-the-host-fluently)).

Then reference the `AlbaHost` in tests like this sample:

<!-- snippet: sample_NUnit_scenario_test -->
Expand Down
8 changes: 5 additions & 3 deletions docs/guide/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
To stub out all possible authentication inside your ASP.NET Core system, you can use the
`AuthenticationStub` to automatically authenticate every request and build out a `ClaimsPrincipal` to your specification.

Here's a sample of bootstrapping an `AlbaHost` with the `AuthenticationStub`:
Here's a sample of bootstrapping an `AlbaHost` with the `AuthenticationStub`. Security extensions can be passed as arguments to
`AlbaHost.For(...)` as shown here, or chained onto the returned builder with `WithExtension(...)`:

<!-- snippet: sample_bootstrapping_with_stub_extension -->
<a id='snippet-sample_bootstrapping_with_stub_extension'></a>
Expand Down Expand Up @@ -102,9 +103,10 @@ var securityStub = new AuthenticationStub("custom")
.With(JwtRegisteredClaimNames.Email, "guy@company.com")
.WithName("jeremy");

await using var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>(securityStub);
await using var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>()
.WithExtension(securityStub);
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Security/web_api_authentication_with_individual_stub.cs#L21-L31' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_with_stub_scheme_extension' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Security/web_api_authentication_with_individual_stub.cs#L21-L32' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_with_stub_scheme_extension' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Integration with JWT Authentication
Expand Down
3 changes: 3 additions & 0 deletions docs/guide/tunit.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public sealed class AlbaBootstrap : IAsyncInitializer, IAsyncDisposable
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/TUnitSamples/Program.cs#L6-L21' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_TUnit_Application' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Any Alba specific setup, such as configuration overrides or extensions, is chained onto `AlbaHost.For<T>()` before it is awaited
(see [Configuring the Host Fluently](gettingstarted.md#configuring-the-host-fluently)).

Then inject the instance by adding `[ClassDataSource<AlbaBootstrap>(Shared = SharedType.PerTestSession)]` to your test class. We recommend creating a base class to allow easier access of the host and any other dependencies.

<!-- snippet: sample_TUnit_scenario_test -->
Expand Down
22 changes: 13 additions & 9 deletions docs/guide/xunit.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ you'll get *unusual* behavior. Also note that you really need to dispose the `Al

If your application startup time becomes a performance problem, and especially in larger test suites, you probably want to share the `AlbaHost` object between tests. xUnit helpfully provides the [class fixture feature](https://xunit.net/docs/shared-context) for just this use case.

Build out your `AlbaHost` in a class like this:
Build out your `AlbaHost` in a class like this. ASP.NET Core setup goes in the configuration action, and Alba specific setup such as
configuration overrides and extensions is chained onto the builder before it is awaited (see
[Configuring the Host Fluently](gettingstarted.md#configuring-the-host-fluently)):

<!-- snippet: sample_xUnit_Fixture_net6 -->
<a id='snippet-sample_xUnit_Fixture_net6'></a>
Expand All @@ -43,9 +45,11 @@ public class WebAppFixture : IAsyncLifetime
public async ValueTask InitializeAsync()
{
AlbaHost = await Alba.AlbaHost.For<WebApp.Program>(builder =>
{
// Configure all the things
});
{
// ASP.NET Core host setup such as service registrations
})
// Alba specific setup such as configuration overrides and extensions
.WithConfiguration("Alba:Fixture", "xUnit");
}

public async ValueTask DisposeAsync()
Expand All @@ -54,7 +58,7 @@ public class WebAppFixture : IAsyncLifetime
}
}
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L6-L25' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_xUnit_Fixture_net6' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L6-L27' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_xUnit_Fixture_net6' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Then in your actual xUnit fixture classes, implement the `IClassFixture<T>` class like this:
Expand All @@ -71,7 +75,7 @@ public class ContractTestWithAlba : IClassFixture<WebAppFixture>

private readonly IAlbaHost _host;
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L28-L37' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_xUnit_Fixture' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L30-L39' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_xUnit_Fixture' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Collection Fixtures
Expand All @@ -90,7 +94,7 @@ public class ScenarioCollection : ICollectionFixture<WebAppFixture>

}
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L74-L82' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ScenarioCollection' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L76-L84' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ScenarioCollection' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

As a convenience, I like to have a base class for all test fixture classes that will be using scenarios like this:
Expand All @@ -109,7 +113,7 @@ public abstract class ScenarioContext
public IAlbaHost Host { get; }
}
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L84-L97' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ScenarioContext' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L86-L99' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_ScenarioContext' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

And then inherit from that `ScenarioContext` base class in actual test fixture classes:
Expand All @@ -134,5 +138,5 @@ public class sample_integration_fixture : ScenarioContext
}
}
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L99-L118' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_integration_fixture' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Samples/ContractTestWithAlba.cs#L101-L120' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_integration_fixture' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
5 changes: 3 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature"
"version": "11.0.100-rc.1.26425.128",
"rollForward": "latestFeature",
"allowPrerelease": true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
=> throw new UriFormatException("Invalid URI: simulated OData formatter failure");
}

private static Task<IAlbaHost> createHost() =>
private static AlbaHostBuilder createHost() =>
AlbaHost.For<WebApp.Program>(x =>
{
x.ConfigureServices(services => services.PostConfigure<MvcOptions>(o =>
Expand Down
Loading
Loading