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
20 changes: 13 additions & 7 deletions CSF.Screenplay.Abstractions/IPersona.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

namespace CSF.Screenplay
{
/// <summary>A persona is a factory for a commonly-used actor</summary>
/// <summary>A persona is a factory for a commonly-used <see cref="Actor"/></summary>
/// <remarks>
/// <para>
/// In Screenplay is is recommended to use memorable actors which are widely understood and recognisable
/// by the team. This is easier if the composition of an actor is the same across every <see cref="IPerformance"/> in which
/// they participate.
/// In Screenplay is is recommended to use &amp; reuse memorable actors, which are widely understood and recognisable
/// to the development team. This is easier if the composition of an actor is the same across every
/// <see cref="IPerformance"/> in which they participate. Personas facilitate this; the persona class serves
/// as a consistent factory which creates the same named actor in the same manner every time.
/// </para>
/// <para>
/// By using a separate persona implementation for each named actor, the developer can ensure consistent creation
/// for instances of those actors.
/// Developers should create an implementation of this interface for each actor which they wish to
/// define, ideally named the same as the name as the Actor. Each persona implementation should
/// name the actor and grant them <xref href="AbilityGlossaryItem?text=the+abilities"/> which are appropriate.
/// </para>
/// <para>
/// Instance of persona classes are resolved from the Dependency Injection container, so it is appropriate
/// and correct to constructor-inject any services which are required in order to get the ability instances.
/// </para>
/// </remarks>
public interface IPersona : IHasName
Expand All @@ -26,4 +32,4 @@ public interface IPersona : IHasName
/// <param name="performanceIdentity">A unique identity for the currently-executing performance</param>
Actor GetActor(Guid performanceIdentity);
}
}
}
2 changes: 1 addition & 1 deletion CSF.Screenplay.Docs/docs/GettingReports.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The default filename includes a representation of the current timestamp, so each
This path may be altered by specifying a different set of options to the [`AddScreenplay`] service collection extension method.

[`ScreenplayOptions.ReportPath`]: xref:CSF.Screenplay.ScreenplayOptions.ReportPath
[`AddScreenplay`]: xref:CSF.Screenplay.ScreenplayServiceCollectionExtensions.AddScreenplay(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Action{CSF.Screenplay.ScreenplayOptions})
[`AddScreenplay`]: xref:CSF.Screenplay.ScreenplayServiceCollectionExtensions.AddScreenplay(Microsoft.Extensions.DependencyInjection.IServiceCollection)

## Converting JSON to a human-readable report

Expand Down
10 changes: 5 additions & 5 deletions CSF.Screenplay.Docs/docs/bestPractice/WritingTests.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Advice for writing tests with Screenplay

Whichever testing integration you choose to use, there are some general pieces of advice and best practice which apply.
Whichever testing integration you choose to use, there are some general pieces of advice and best practice which apply.

## Use high-level tasks

Expand Down Expand Up @@ -38,16 +38,16 @@ Assertion libraries to consider include [Shouldly] and [Fluent Assertions].
## Do not include assertions in performables

It might be tempting to include assertion syntax within [performables] such as [tasks].
This is not recommended.
This is not recommended.
Where assertions appear within performables:

* They reduce the reusability of the performables
* They create a dependency between your performables and your testing framework/assertion library
* They can make test logic harder to read and understand

It is recommended to keep your performables/tasks free from assertions.
In your test code, use [questions] or question-like tasks to get values/data from the system under test.
It is recommended to keep your performables/tasks free from assertions.
In your test code, use [questions] or question-like tasks to get values/data from the system under test.
Write your assertions in your main test logic, asserting that the values retrieved are as-expected.

[tasks]: ../../glossary/Task.md
[questions]: ../../glossary/Question.md
[questions]: ../../glossary/Question.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ If Screenplay is not being used for testing, then the consuming logic has the fo

[Performance]: xref:CSF.Screenplay.IPerformance
[`Screenplay`]: xref:CSF.Screenplay.Screenplay
[`Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{CSF.Screenplay.ScreenplayOptions})
[`Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection})
[`BeginScreenplay`]: xref:CSF.Screenplay.Screenplay.BeginScreenplay
[`ExecuteAsPerformanceAsync<TPerformance>`]: xref:CSF.Screenplay.ScreenplayExtensions.ExecuteAsPerformanceAsync``1(CSF.Screenplay.Screenplay,System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)
[`ExecuteAsPerformance`]: xref:CSF.Screenplay.ScreenplayExtensions.ExecuteAsPerformance(CSF.Screenplay.Screenplay,System.Func{System.IServiceProvider,System.Nullable{System.Boolean}},System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
There are two techniques to add dependency services to Screenplay.
You may either integrate Screenplay into an existing container for your application or tests or you may add additional services via the static [`Create`] factory method.

[`Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{CSF.Screenplay.ScreenplayOptions})
[`Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection})

## Integrating with an existing container

Expand All @@ -29,7 +29,7 @@ var screenplay = Screenplay.Create(services => {
});
```

[`Screenplay.Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{CSF.Screenplay.ScreenplayOptions})
[`Screenplay.Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection})

## A reminder on lifetime scopes

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Injecting services into Personas

Types which derive from [`IPersona`] support constructor-injected dependencies.
Personas are typically used by either [the cast] or [the stage] to get an [Actor].
The technique in which they are used means that they are resolved, along with their constructor-injected dependencies, from DI.

Use constructor-injected dependencies in persona classes to provide access to the APIs required to resolve [Abilities] that the actor is to be granted.

[`IPersona`]: xref:CSF.Screenplay.IPersona
[the cast]: xref:CSF.Screenplay.ICast
[the stage]: xref:CSF.Screenplay.IStage
[Actor]: xref:CSF.Screenplay.Actor
[Abilities]: ../../glossary/Ability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Constructor injection for bindings

For frameworks which are based on **binding classes** such as [Reqnroll], services are constructor-injected into binding classes.
Use dependencies injected in this way to get access to [commonly-used Screenplay services] and anything else required at the root level of your test logic.

[Reqnroll]: https://reqnroll.net/
[commonly-used Screenplay services]: InjectableServices.md

## Example

This is an example of a Reqnroll-style binding class with a constructor-injected dependency.
This would require [installing the Reqnroll test integration] to run.
Take note of the `stage` parameter in the primary constructor.

```csharp
[Binding]
public class SampleSteps(IStage stage)
{
[Given("Simon shows a sample step")]
public async Task GivenSimonShowsASampleStep()
{
var simon = stage.Spotlight<Simon>();

// ... use the actor with some performables, then make assertions etc
}
}
```

[installing the Reqnroll test integration]: ../gettingStarted/reqnroll/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ uid: DependencyInjectionScopeArticle
# Dependency injection scope

Developers familiar with dependency injection are likely to be familiar with the concept of [DI Scopes].
That is - some services which are designated as _scoped_ or _instance per scope_ use a common/shared instance for the lifetime/duration of the scope.
Screenplay uses this concept; a number of its services are added to the container with _a per-scope lifetime_.
Screenplay creates **a new DI scope per [Performance]**.
As you can see on [the diagram of how Actors, Abilities and Performables relate to one another], each [`Screenplay`] contains and executes many performances.
This scope-creation is handled automatically by the Screenplay framework logic.

Within Screenplay logic, a DI scope is automatically created, with a lifetime matching that of the current [Performance].
Within a performance, when any of the scoped services (listed below) are injected, each point of injection will receive the same shared instance of that service.
Instances are independent per-performance; each performance gets its own shared instance of each of the listed services.

[DI Scopes]: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes
[Performance]: xref:CSF.Screenplay.Performance
[the diagram of how Actors, Abilities and Performables relate to one another]: ../concepts/MakeupOfAScreenplay.md
[`Screenplay`]: xref:CSF.Screenplay.Screenplay

## List of scoped services

Expand All @@ -25,6 +29,7 @@ The following services are added to DI "per lifetime scope".
## List of singleton services

The following services are added to DI as singletons.
There is only ever a single instance of these services per Screenplay.

* [The Screenplay](xref:CSF.Screenplay.Screenplay)
* [The event bus](xref:CSF.Screenplay.Performances.PerformanceEventBus)
Expand Down
73 changes: 16 additions & 57 deletions CSF.Screenplay.Docs/docs/dependencyInjection/InjectingServices.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,24 @@
---
uid: InjectingServicesArticle
---

# Injecting services

There are a few techniques in which injecting dependencies is relevant. These are summarised below.
All but one of these techniques provide access to [the services which are added to the container].

[the services which are added to the container]: InjectableServices.md

## Into test logic

When using automated test logic based upon Screenplay, the use of dependency injection typically takes one of two forms.
Which of these depends upon the nature and paradigm of the test framework.

For frameworks which are based on **test methods** such as [NUnit], services are typically injected via _method parameter injection_ into the test methods.
[If Screenplay were to be extended] to work with frameworks such as xUnit or MSTest then this is likely to be the technique used.

For frameworks which are based on **binding classes** such as [Reqnroll], services are constructor-injected into binding classes.

Use dependencies injected in this way to get access to [commonly-used Screenplay services] and anything else required at the root level of your test logic.

[NUnit]: https://nunit.org/
[If Screenplay were to be extended]: ../extendingScreenplay/TestIntegrations.md
[Reqnroll]: https://reqnroll.net/
[commonly-used Screenplay services]: InjectableServices.md

## Into standalone performance logic
The table below summarises how to get services, from various contexts. In the first column are contexts/situations in which a developer could require services from DI. The second column indicates and links to the appropriate technique for that scenario.

If you are [using Screenplay standalone] then the [`Screenplay.ExecuteAsPerformanceAsync`] permits resolution of dependencies via its parameter.
That parameter is a `Func<IServiceProvider,CancellationToken,Task<bool?>>`.
The service provider may be used to resolve dependency services for the performance's logic.
All but one of these techniques (the last) provides full access to [the services which are added to the container].

Developers are urged to consider encapsulating their performance logic in implementations of [`IHostsPerformance`].
Through an overload (extension method) named [`ExecuteAsPerformanceAsync<T>`], developers may specify the concrete implementation of that interface.
This extension method will resolve that implementation type along with any of its constructor-injected dependencies.
This avoids the service locator anti-pattern and provides a convenient pattern by which to write performance logic.
| Context | Technique |
| ---------------------------------------------------- | -------------------------------------------------------- |
| NUnit style test method | [Parameter inject into the test] method |
| Reqnroll style binding class | [Constructor inject into the binding] class |
| Standalone Screenplay: [`ExecuteAsPerformanceAsync`] | [Create a performance host, or use the service provider] |
| Persona class | [Constructor inject into the Persona] class |
| Performable class, such as a Task | [Use the actor's abilities] |

Use services resolved from the service provider, or injected into your [`IHostsPerformance`] implementation, to get access to [commonly-used Screenplay services] and anything else required at the root level of your performance logic.

[using Screenplay standalone]: ../gettingStarted/nonTesting/index.md
[`Screenplay.ExecuteAsPerformanceAsync`]: xref:CSF.Screenplay.Screenplay.ExecuteAsPerformanceAsync(System.Func{System.IServiceProvider,System.Threading.CancellationToken,System.Threading.Tasks.Task{System.Nullable{System.Boolean}}},System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)
[`IHostsPerformance`]: xref:CSF.Screenplay.IHostsPerformance
[`ExecuteAsPerformanceAsync<T>`]: xref:CSF.Screenplay.ScreenplayExtensions.ExecuteAsPerformanceAsync``1(CSF.Screenplay.Screenplay,System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)

## Into personas

Types which derive from [`IPersona`] support constructor-injected dependencies.
Personas are typically used by either [the cast] or [the stage] to get an [Actor].
The technique in which they are used means that they are resolved, along with their constructor-injected dependencies, from DI.

Use constructor-injected dependencies in persona classes to provide access to the APIs required to resolve [Abilities] that the actor is to be granted.

[`IPersona`]: xref:CSF.Screenplay.IPersona
[the cast]: xref:CSF.Screenplay.ICast
[the stage]: xref:CSF.Screenplay.IStage
[Actor]: xref:CSF.Screenplay.Actor
[Abilities]: ../../glossary/Ability.md

## Into performables

_See the article explaining [how performables get their dependencies]_.

[how performables get their dependencies]: Performables.md
[the services which are added to the container]: InjectableServices.md
[Parameter inject into the test]: ParameterInjectionForTests.md
[Constructor inject into the binding]: ConstructorInjectionForBindings.md
[Create a performance host, or use the service provider]: InjectionForStandaloneScreenplay.md
[Constructor inject into the Persona]: ConstructorInjectIntoThePersona.md
[`ExecuteAsPerformanceAsync`]: xref:CSF.Screenplay.Screenplay.ExecuteAsPerformanceAsync(System.Func{System.IServiceProvider,System.Threading.CancellationToken,System.Threading.Tasks.Task{System.Nullable{System.Boolean}}},System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)
[Use the actor's abilities]: Performables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Dependency injection for standalone Screenplay

If you are [using Screenplay standalone] then you are likely using the method [`Screenplay.ExecuteAsPerformanceAsync`] or one of its overloads.

[using Screenplay standalone]: ../gettingStarted/nonTesting/index.md
[`Screenplay.ExecuteAsPerformanceAsync`]: xref:CSF.Screenplay.Screenplay.ExecuteAsPerformanceAsync(System.Func{System.IServiceProvider,System.Threading.CancellationToken,System.Threading.Tasks.Task{System.Nullable{System.Boolean}}},System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)

## Recommended: Use `IHostsPerformance`

Developers are urged to consider encapsulating their performance logic in a class which derives from [`IHostsPerformance`].
Through an overload (extension method) named [`ExecuteAsPerformanceAsync<T>`], developers may specify the concrete implementation type of that class as a generic type parameter.
This overload of `ExecuteAsPerformanceAsync` will resolve your class which derives from `IHostsPerformance` from dependency injection (_remember to add it to the container!_).
This resolution will include all the class' dependencies which are constructor-injected and leads to very clean code.
The service provider is never exposed, thus removing the temptation to use the service locator anti-pattern.

[`IHostsPerformance`]: xref:CSF.Screenplay.IHostsPerformance
[`ExecuteAsPerformanceAsync<T>`]: xref:CSF.Screenplay.ScreenplayExtensions.ExecuteAsPerformanceAsync``1(CSF.Screenplay.Screenplay,System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)

### Example of the performance host technique

This example is functionally identical to the example below.

```csharp
public class MyPerformance(ICast cast) : IHostsPerformance
{
public Task<bool?> ExecutePerformanceAsync(CancellationToken cancellationToken)
{
var aaron = cast.GetActor<Aaron>();
// Further performance logic ...
}
}

// To consume the above:
await screenplay.ExecuteAsPerformanceAsync<MyPerformance>();
```

## Alternative: Use a function

The original [`Screenplay.ExecuteAsPerformanceAsync`] overload accepts up to three parameters, the first of which is a function.
That function should contain the logic which makes up the performance.
The function (which drives the performance logic) provides two parameters itself:

* An [`IServiceProvider`](xref:System.IServiceProvider)
* A [`CancellationToken`](xref:System.Threading.CancellationToken)

The serivce provider may be used to resolve services which may be used by the performance logic.

### Example of the function-based technique

This example is functionally identical to the example above.

```csharp
await screenplay.ExecuteAsPerformanceAsync((services, cancellationToken) => {
var cast = services.GetRequiredService<ICast>();
var aaron = cast.GetActor<Aaron>();
// Further performance logic ...
});

```
Loading
Loading