Skip to content

DependencyInjection

Craig Fowler edited this page Mar 18, 2019 · 2 revisions

Automated test logic should be simple: easy to read and understand. On the other hand, Screenplay is usable alongside complex dependency objects, such as WebDrivers, which require non-trivial configuration and set-up. The solution to this paradox is dependency injection (aka DI).

Screenplay includes and uses a small DI framework: FlexDi which is configurable from your integration configuration.

Do not confuse Screenplay's DI with the DI which you might use in your application itself. Screenplay's DI is used only to retrieve dependencies for running the tests.

Recommended use in tests

The recommended way to use DI in test logic is to inject either a cast or stage object into your test, and then to get your actors using personas.

The cast and stage are both dependency-injection-aware. They will provide a dependency-resolver to the persona when creating an actor. In turn, this means that all of an actor's abilities may be created via DI, often meaning that nothing else needs to be injected directly into your test logic.

Using DI with Personas

To inject dependencies into an actor, as they are created by a persona, override the GrantAbilities method in your persona class. The second parameter - of type IResolvesServices - provides access to DI and may be used to get any registered dependency.

public virtual void GrantAbilities(ICanReceiveAbilities actor, IResolvesServices resolver);

Getting dependencies: NUnit

In an NUnit test, dependencies are injected into the test method as parameters. The test must be decorated with the [Screenplay] attribute for injection to occur. Here is an example:

[Test, Screenplay]
public void SampleTest(ICast cast)
{
  // Test logic here; the ICast parameter
  // is handled by dependency injection
}

Getting dependencies: SpecFlow

Presuming that your SpecFlow tests are configured to work with Screenplay, then receiving dependencies into your binding classes is as simple as adding the dependencies to the constructor:

[Binding]
public class MySampleSteps
{
  readonly IStage stage;

  [Given("A sample binding")]
  public void GivenASampleBinding()
  {
    // Binding logic here; the stage
    // has been provided by DI
  }

  public MySampleSteps(IStage stage)
  {
    this.stage = stage;
  }
}

SpecFlow includes a small DI framework out-of-the-box but Screenplay replaces that with it its own. If you need to manually register any dependencies for SpecFlow then you should do this using Screenplay's integration configuration.

Dependency sharing & scope

Screenplay defines and uses two dependency scopes: per scenario and per test run. The scope to which a dependency belongs depends upon where it is registered in the DI section of the integration configuration.

Dependencies registered per test run are only created once throughout an entire test run. Every scenario which uses that dependency shares the same dependency object.

Dependencies registered per scenario are created a maximum of once per test/scenario. Each scenario receives a fresh dependency.

Regardless of the scope, all dependencies are created lazily. The object instance is constructed upon first usage within its respective scope. For example: A dependency which is registered per-scenario is not created for scenarios which don't make use of it.

Further reading

The FlexDi dependency injection framework used with Screenplay has full documentation available on its own project site.