Skip to content
136 changes: 67 additions & 69 deletions docs/site/docs/getting-started/create-test-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,67 @@ To write tests, you need a place to put them - a test project. bUnit is not a un

To use bUnit, the easiest approach is to use the bUnit project template described in the [Create a test project with bUnit template](#creating-a-test-project-with-bunit-template) section further down the page. To create a test project manually and in a general-purpose testing frameworks agnostic way, read the following section.

## Creating a test project with bUnit template

To skip a few steps in the guide above, use the [bUnit test project template](https://www.nuget.org/packages/bunit.template/).

The steps for creating a test project with the bUnit template are as follows:

1. Install the template (only needed the first time)
2. Create a new test project
3. Add the test project to your solution

These steps look like this from the `dotnet` CLI:

**1. Install the template**

Install the template from NuGet using this command:

```dotnetcli
dotnet new --install bunit.template
```

**2. Create a new test project**

Use the following command to create a bUnit with xUnit test project:

# [xUnit](#tab/xunit)

```dotnetcli
dotnet new bunit --framework xunit -o <NAME OF TEST PROJECT>
```

# [NUnit](#tab/nunit)

```dotnetcli
dotnet new bunit --framework nunit -o <NAME OF TEST PROJECT>
```

# [MSTest](#tab/mstest)

```dotnetcli
dotnet new bunit --framework mstest -o <NAME OF TEST PROJECT>
```

***

The `--framework` option in the `dotnet new` command above is used to specify the unit testing framework used by the test project. If the `--framework` option is omitted, the default test framework `xunit` will be configured. Currently supported options are the following:

- `xunit` - [xUnit](https://xunit.net/),
- `nunit` - [NUnit](https://nunit.org/),
- `mstest` - [MSTest](https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest)

**3. Add the test project to your solution**

If using Visual Studio, add the test project to your solution (`.sln`), and add a reference between the test project and the project containing the components that should be tested:

```dotnetcli
dotnet sln <NAME OF PROJECT>.sln add <NAME OF TEST PROJECT>
dotnet add <NAME OF TEST PROJECT>.csproj reference <NAME OF COMPONENT PROJECT>.csproj
```

This will allow the test project to see and test the components in the component project.

## Creating a test project manually

This section will take you through the steps required to create a project for testing Blazor components using bUnit. Any of the three general-purpose test frameworks shown in step 1 below can be used. Briefly, here is what we will do:
Expand Down Expand Up @@ -116,7 +177,7 @@ The result should be a test project with a `.csproj` that looks like this (non b
<ItemGroup>
<ProjectReference Include="<PATH TO COMPONENT LIB>.csproj" />
</ItemGroup>

</Project>
```

Expand All @@ -132,13 +193,13 @@ The result should be a test project with a `.csproj` that looks like this (non b
</PropertyGroup>

<ItemGroup>
<PackageReference Include="bunit" Version="#{RELEASE-VERSION}#" />
<PackageReference Include="bunit" Version="#{RELEASE-VERSION}#" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="<PATH TO COMPONENT LIB>.csproj" />
</ItemGroup>
Expand All @@ -158,83 +219,20 @@ The result should be a test project with a `.csproj` that looks like this (non b
</PropertyGroup>

<ItemGroup>
<PackageReference Include="bunit" Version="#{RELEASE-VERSION}#" />
<PackageReference Include="bunit" Version="#{RELEASE-VERSION}#" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="<PATH TO COMPONENT LIB>.csproj" />
</ItemGroup>

</Project>
```

***

## Creating a test project with bUnit template

To skip a few steps in the guide above, use the [bUnit test project template](https://www.nuget.org/packages/bunit.template/).

The steps for creating a test project with the bUnit template are as follows:

1. Install the template (only needed the first time)
2. Create a new test project
3. Add the test project to your solution

These steps look like this from the `dotnet` CLI:

**1. Install the template**

Install the template from NuGet using this command:

```dotnetcli
dotnet new install bunit.template::#{NBGV_NuGetPackageVersion}#
```

**2. Create a new test project**

Use the following command to create a bUnit with xUnit test project:

# [xUnit](#tab/xunit)

```dotnetcli
dotnet new bunit --framework xunit -o <NAME OF TEST PROJECT>
```

# [NUnit](#tab/nunit)

```dotnetcli
dotnet new bunit --framework nunit -o <NAME OF TEST PROJECT>
```

# [MSTest](#tab/mstest)

```dotnetcli
dotnet new bunit --framework mstest -o <NAME OF TEST PROJECT>
```

***

The `--framework` option in the `dotnet new` command above is used to specify the unit testing framework used by the test project. If the `--framework` option is omitted, the default test framework `xunit` will be configured. Currently supported options are the following:

- `xunit` - [xUnit](https://xunit.net/),
- `nunit` - [NUnit](https://nunit.org/),
- `mstest` - [MSTest](https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest)

**3. Add the test project to your solution**

If using Visual Studio, add the test project to your solution (`.sln`), and add a reference between the test project and the project containing the components that should be tested:

```dotnetcli
dotnet sln <NAME OF PROJECT>.sln add <NAME OF TEST PROJECT>
dotnet add <NAME OF TEST PROJECT>.csproj reference <NAME OF COMPONENT PROJECT>.csproj
```

This will allow the test project to see and test the components in the component project.

## Further reading

To start creating tests, continue reading the <xref:writing-tests> page.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.109" PrivateAssets="All" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.113" PrivateAssets="All" />

</ItemGroup>

Expand Down
42 changes: 20 additions & 22 deletions src/bunit.core/Extensions/WaitForHelpers/WaitForHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Bunit.Extensions.WaitForHelpers;
/// </summary>
public abstract class WaitForHelper<T> : IDisposable
{
private readonly Timer timer;
private readonly TaskCompletionSource<T> checkPassedCompletionSource;
private readonly Func<(bool CheckPassed, T Content)> completeChecker;
private readonly IRenderedFragmentBase renderedFragment;
Expand Down Expand Up @@ -51,7 +52,13 @@ protected WaitForHelper(

logger = renderedFragment.Services.CreateLogger<WaitForHelper<T>>();
checkPassedCompletionSource = new TaskCompletionSource<T>();
WaitTask = CreateWaitTask(renderedFragment, timeout);
timer = new Timer(_ =>
{
logger.LogWaiterTimedOut(renderedFragment.ComponentId);
checkPassedCompletionSource.TrySetException(new WaitForFailedException(TimeoutErrorMessage, capturedException));
});
WaitTask = CreateWaitTask(renderedFragment);
timer.Change(GetRuntimeTimeout(timeout), Timeout.InfiniteTimeSpan);

InitializeWaiting();
}
Expand Down Expand Up @@ -80,6 +87,7 @@ protected virtual void Dispose(bool disposing)
return;

isDisposed = true;
timer.Dispose();
checkPassedCompletionSource.TrySetCanceled();
renderedFragment.OnAfterRender -= OnAfterRender;
logger.LogWaiterDisposed(renderedFragment.ComponentId);
Expand All @@ -105,41 +113,30 @@ private void InitializeWaiting()
}
}

private Task<T> CreateWaitTask(IRenderedFragmentBase renderedFragment, TimeSpan? timeout)
private Task<T> CreateWaitTask(IRenderedFragmentBase renderedFragment)
{
var renderer = renderedFragment.Services.GetRequiredService<ITestRenderer>();
var renderer = renderedFragment
.Services
.GetRequiredService<ITestRenderer>();

// Two to failure conditions, that the renderer captures an unhandled
// exception from a component or itself, or that the timeout is reached,
// are executed on the renderes scheduler, to ensure that OnAfterRender
// and the continuations does not happen at the same time.
var failureTask = renderer.Dispatcher.InvokeAsync(() =>
{
var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

var renderException = renderer
return renderer
.UnhandledException
.ContinueWith(
x => Task.FromException<T>(x.Result),
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously,
taskScheduler);

var timeoutTask = Task.Delay(GetRuntimeTimeout(timeout))
.ContinueWith(
x =>
{
logger.LogWaiterTimedOut(renderedFragment.ComponentId);
return Task.FromException<T>(new WaitForFailedException(TimeoutErrorMessage, capturedException));
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously,
taskScheduler);

return Task.WhenAny(renderException, timeoutTask).Unwrap();
TaskScheduler.FromCurrentSynchronizationContext());
}).Unwrap();

return Task.WhenAny(failureTask, checkPassedCompletionSource.Task).Unwrap();
return Task
.WhenAny(checkPassedCompletionSource.Task, failureTask)
.Unwrap();
}

private void OnAfterRender(object? sender, EventArgs args)
Expand Down Expand Up @@ -170,7 +167,8 @@ private void OnAfterRender(object? sender, EventArgs args)

if (StopWaitingOnCheckException)
{
checkPassedCompletionSource.TrySetException(new WaitForFailedException(CheckThrowErrorMessage, capturedException));
checkPassedCompletionSource.TrySetException(
new WaitForFailedException(CheckThrowErrorMessage, capturedException));
Dispose();
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<ItemGroup Condition="$(MSBuildProjectName) != 'bunit.testassets'">
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="Shouldly" Version="4.1.0" />
<PackageReference Include="xunit" Version="2.4.2" />
Expand Down
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.10",
"version": "1.11-preview",
"assemblyVersion": {
"precision": "revision"
},
Expand Down