Skip to content

Commit d1a1abe

Browse files
committed
Resolve #269 - Complete DI docco
1 parent 01158f8 commit d1a1abe

14 files changed

Lines changed: 158 additions & 62 deletions

CSF.Screenplay.Abstractions/IPersona.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@
22

33
namespace CSF.Screenplay
44
{
5-
/// <summary>A persona is a factory for a commonly-used actor</summary>
5+
/// <summary>A persona is a factory for a commonly-used <see cref="Actor"/></summary>
66
/// <remarks>
77
/// <para>
88
/// In Screenplay is is recommended to use &amp; reuse memorable actors, which are widely understood and recognisable
9-
/// to the development team. This is easier if the composition of an actor is the same across every <see cref="IPerformance"/> in which
10-
/// they participate.
9+
/// to the development team. This is easier if the composition of an actor is the same across every
10+
/// <see cref="IPerformance"/> in which they participate. Personas facilitate this; the persona class serves
11+
/// as a consistent factory which creates the same named actor in the same manner every time.
1112
/// </para>
1213
/// <para>
13-
/// Use classes which derive from this interface to define those memorable
14-
/// actors. The persona class should configure the actor it creates with a name and
15-
/// a set of abilities appropriate to the actor.
14+
/// Developers should create an implementation of this interface for each actor which they wish to
15+
/// define, ideally named the same as the name as the Actor. Each persona implementation should
16+
/// name the actor and grant them <xref href="AbilityGlossaryItem?text=the+abilities"/> which are appropriate.
1617
/// </para>
1718
/// <para>
18-
/// Classes which derive from this interface should constructor-inject any dependencies
19-
/// they require, such as those required to grant abilities.
19+
/// Instance of persona classes are resolved from the Dependency Injection container, so it is appropriate
20+
/// and correct to constructor-inject any services which are required in order to get the ability instances.
2021
/// </para>
2122
/// </remarks>
2223
public interface IPersona : IHasName

CSF.Screenplay.Docs/docs/GettingReports.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The default filename includes a representation of the current timestamp, so each
1717
This path may be altered by specifying a different set of options to the [`AddScreenplay`] service collection extension method.
1818

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

2222
## Converting JSON to a human-readable report
2323

CSF.Screenplay.Docs/docs/bestPractice/WritingTests.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Advice for writing tests with Screenplay
22

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

55
## Use high-level tasks
66

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

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

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

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

5252
[tasks]: ../../glossary/Task.md
53-
[questions]: ../../glossary/Question.md
53+
[questions]: ../../glossary/Question.md

CSF.Screenplay.Docs/docs/concepts/HowScreenplayAndPerformanceRelate.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ If Screenplay is not being used for testing, then the consuming logic has the fo
2525

2626
[Performance]: xref:CSF.Screenplay.IPerformance
2727
[`Screenplay`]: xref:CSF.Screenplay.Screenplay
28-
[`Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{CSF.Screenplay.ScreenplayOptions})
28+
[`Create`]: xref:CSF.Screenplay.Screenplay.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection})
2929
[`BeginScreenplay`]: xref:CSF.Screenplay.Screenplay.BeginScreenplay
3030
[`ExecuteAsPerformanceAsync<TPerformance>`]: xref:CSF.Screenplay.ScreenplayExtensions.ExecuteAsPerformanceAsync``1(CSF.Screenplay.Screenplay,System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)
3131
[`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)

CSF.Screenplay.Docs/docs/dependencyInjection/AddingServices.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
There are two techniques to add dependency services to Screenplay.
44
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.
55

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

88
## Integrating with an existing container
99

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

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

3434
## A reminder on lifetime scopes
3535

CSF.Screenplay.Docs/docs/dependencyInjection/ConstructorInjectIntoThePersona.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Injecting services into Personas
2+
23
Types which derive from [`IPersona`] support constructor-injected dependencies.
34
Personas are typically used by either [the cast] or [the stage] to get an [Actor].
45
The technique in which they are used means that they are resolved, along with their constructor-injected dependencies, from DI.
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
11
# Constructor injection for bindings
2-
For frameworks which are based on **binding classes** such as [Reqnroll], services are constructor-injected into binding classes.
32

3+
For frameworks which are based on **binding classes** such as [Reqnroll], services are constructor-injected into binding classes.
44
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.
55

66
[Reqnroll]: https://reqnroll.net/
77
[commonly-used Screenplay services]: InjectableServices.md
8+
9+
## Example
10+
11+
This is an example of a Reqnroll-style binding class with a constructor-injected dependency.
12+
This would require [installing the Reqnroll test integration] to run.
13+
Take note of the `stage` parameter in the primary constructor.
14+
15+
```csharp
16+
[Binding]
17+
public class SampleSteps(IStage stage)
18+
{
19+
[Given("Simon shows a sample step")]
20+
public async Task GivenSimonShowsASampleStep()
21+
{
22+
var simon = stage.Spotlight<Simon>();
23+
24+
// ... use the actor with some performables, then make assertions etc
25+
}
26+
}
27+
```
28+
29+
[installing the Reqnroll test integration]: ../gettingStarted/reqnroll/index.md

CSF.Screenplay.Docs/docs/dependencyInjection/DependencyInjectionScope.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ uid: DependencyInjectionScopeArticle
55
# Dependency injection scope
66

77
Developers familiar with dependency injection are likely to be familiar with the concept of [DI Scopes].
8-
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.
8+
Screenplay uses this concept; a number of its services are added to the container with _a per-scope lifetime_.
9+
Screenplay creates **a new DI scope per [Performance]**.
10+
As you can see on [the diagram of how Actors, Abilities and Performables relate to one another], each [`Screenplay`] contains and executes many performances.
11+
This scope-creation is handled automatically by the Screenplay framework logic.
912

10-
Within Screenplay logic, a DI scope is automatically created, with a lifetime matching that of the current [Performance].
1113
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.
1214
Instances are independent per-performance; each performance gets its own shared instance of each of the listed services.
1315

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

1721
## List of scoped services
1822

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

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

2934
* [The Screenplay](xref:CSF.Screenplay.Screenplay)
3035
* [The event bus](xref:CSF.Screenplay.Performances.PerformanceEventBus)
Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,59 @@
11
# Dependency injection for standalone Screenplay
22

3-
If you are [using Screenplay standalone] then the [`Screenplay.ExecuteAsPerformanceAsync`] permits resolution of dependencies via its parameter.
4-
That parameter is a `Func<IServiceProvider,CancellationToken,Task<bool?>>`.
5-
The service provider may be used to resolve dependency services for the performance's logic.
6-
7-
Developers are urged to consider encapsulating their performance logic in implementations of [`IHostsPerformance`].
8-
Through an overload (extension method) named [`ExecuteAsPerformanceAsync<T>`], developers may specify the concrete implementation of that interface.
9-
This extension method will resolve that implementation type along with any of its constructor-injected dependencies.
10-
This avoids the service locator anti-pattern and provides a convenient pattern by which to write performance logic.
11-
12-
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.
3+
If you are [using Screenplay standalone] then you are likely using the method [`Screenplay.ExecuteAsPerformanceAsync`] or one of its overloads.
134

145
[using Screenplay standalone]: ../gettingStarted/nonTesting/index.md
156
[`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)
7+
8+
## Recommended: Use `IHostsPerformance`
9+
10+
Developers are urged to consider encapsulating their performance logic in a class which derives from [`IHostsPerformance`].
11+
Through an overload (extension method) named [`ExecuteAsPerformanceAsync<T>`], developers may specify the concrete implementation type of that class as a generic type parameter.
12+
This overload of `ExecuteAsPerformanceAsync` will resolve your class which derives from `IHostsPerformance` from dependency injection (_remember to add it to the container!_).
13+
This resolution will include all the class' dependencies which are constructor-injected and leads to very clean code.
14+
The service provider is never exposed, thus removing the temptation to use the service locator anti-pattern.
15+
1616
[`IHostsPerformance`]: xref:CSF.Screenplay.IHostsPerformance
1717
[`ExecuteAsPerformanceAsync<T>`]: xref:CSF.Screenplay.ScreenplayExtensions.ExecuteAsPerformanceAsync``1(CSF.Screenplay.Screenplay,System.Collections.Generic.IList{CSF.Screenplay.Performances.IdentifierAndName},System.Threading.CancellationToken)
18+
19+
### Example of the performance host technique
20+
21+
This example is functionally identical to the example below.
22+
23+
```csharp
24+
public class MyPerformance(ICast cast) : IHostsPerformance
25+
{
26+
public Task<bool?> ExecutePerformanceAsync(CancellationToken cancellationToken)
27+
{
28+
var aaron = cast.GetActor<Aaron>();
29+
// Further performance logic ...
30+
}
31+
}
32+
33+
// To consume the above:
34+
await screenplay.ExecuteAsPerformanceAsync<MyPerformance>();
35+
```
36+
37+
## Alternative: Use a function
38+
39+
The original [`Screenplay.ExecuteAsPerformanceAsync`] overload accepts up to three parameters, the first of which is a function.
40+
That function should contain the logic which makes up the performance.
41+
The function (which drives the performance logic) provides two parameters itself:
42+
43+
* An [`IServiceProvider`](xref:System.IServiceProvider)
44+
* A [`CancellationToken`](xref:System.Threading.CancellationToken)
45+
46+
The serivce provider may be used to resolve services which may be used by the performance logic.
47+
48+
### Example of the function-based technique
49+
50+
This example is functionally identical to the example above.
51+
52+
```csharp
53+
await screenplay.ExecuteAsPerformanceAsync((services, cancellationToken) => {
54+
var cast = services.GetRequiredService<ICast>();
55+
var aaron = cast.GetActor<Aaron>();
56+
// Further performance logic ...
57+
});
58+
59+
```
Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
# Parameter injection for tests
2-
For testing frameworks which are based on **test methods** such as [NUnit], services are injected via _method parameter injection_. This is performed directly into each test method which is decorated with the `[Screenplay]` attribute.
3-
4-
[If Screenplay were to be extended] to work with frameworks such as xUnit or MSTest, then this same technique would be used.
52

3+
For testing frameworks which are based on **test methods** such as [NUnit], services are injected via _method parameter injection_. This is performed directly into each test method which is decorated with the `[Screenplay]` attribute.
64
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.
75

6+
[If Screenplay were to be extended] to work with frameworks such as xUnit or MSTest, then this same technique would be used.
7+
88
[NUnit]: https://nunit.org/
99
[If Screenplay were to be extended]: ../extendingScreenplay/TestIntegrations.md
10-
[commonly-used Screenplay services]: InjectableServices.md
10+
[commonly-used Screenplay services]: InjectableServices.md
11+
12+
## Example
13+
14+
This is an example of an NUnit-style test method with a parameter-injected dependency.
15+
This would require [installing the NUnit test integration] to run.
16+
Take note of the Screenplay attribute and the `cast` parameter.
17+
18+
```csharp
19+
[Test, Screenplay]
20+
public async Task TheSampleTestShouldBeInformative(ICast cast)
21+
{
22+
var janeSmith = cast.GetActor<JaneSmith>();
23+
24+
// ... use the actor with some performables, then make assertions etc
25+
}
26+
```
27+
28+
[installing the NUnit test integration]: ../gettingStarted/nunit3/index.md

0 commit comments

Comments
 (0)