Skip to content
Craig Fowler edited this page Mar 18, 2019 · 5 revisions

Actors are the lynchpins of a Screenplay test; they provide the entry-point by which test scenarios may execute tasks. Creating actor objects manually can be cumbersome though, because of the need to inject dependencies for their abilities.

Test frameworks based on bindings, such as SpecFlow, face an additional challenge. A single actor object must be accessible across the bindings which are used in single test scenario. Those bindings are likely to be spread across a number of classes.

Screenplay includes two objects to help out with these difficulties: the stage and the cast. Generally-speaking your tests should use one or the other of these, not both. They both provide comparable functionality but in different ways, tailored to different kinds of test framework.

SpecFlow: The Stage

The recommended test framework for use with Screenplay is SpecFlow. For this framework the recommended way to create and manage actor objects is the stage.

The stage operates upon the concept of the spotlight. That is, for any given test step, that step is being performed by one actor at a time. Also, it's often desirable for an actor to perform several steps in a row without each step needing to refer to the actor by name again. Consider the following example, written in gherkin.

Given Jenny is looking at a product list
 When she chooses to buy tomatoes
 Then she should see a prompt to confirm her purchase

Of these steps, only the first mentioned the actor by name. The subsequent steps were written with an implicit understanding that there is an actor who is active right now. Here's how that works in a sample binding:

[Binding]
public class MyBindings
{
  readonly IStage stage;

  [Given("Jenny is looking at a product list")]
  public void GivenJennyIsLookingAtAProductList()
  {
    var jenny = stage.ShineTheSpotlightOn<Jenny>();
    // Perform a task as Jenny
  }

  [When("s?he chooses to buy tomatoes")]
  public void WhenTheyChooseToBuyTomatoes()
  {
    var actor = stage.GetTheActorInTheSpotlight();
    // Perform a task with the current actor
  }

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

Notice how the binding which mentioned the actor by name shines the spotlight on that actor, retrieving them and also marking them as active. Steps which refer to "he/she/they" simply retrieve the actor from the spotlight.

Any time an actor is requested from the stage explicitly by name or persona, if they do not already exist in the test scenario then they are created. Likewise if they do already exist then the existing actor is returned. This means that there is no need for bindings to keep track of which actors they have used already.

You might also notice that the example above combined the use of a persona to retrieve the "Jenny" actor. Where personas are used with the stage, dependency injection of abilities may occur, simplifying the process of creating the actor.

Using the Screenplay SpecFlow integration, the stage object is shared across all bindings in the same test scenario. It does not matter if your bindings are in the same class (like above) or in many classes. Inject the stage into the constructor of each and - within each test scenario - they will receive the same instance of the stage.

NUnit: The Cast

The cast is a per-test-scenario registry of the actors which were used within that scenario, whilst also serving as a factory. It is suited to tests which are written using NUnit or other test frameworks where the test steps are contained within a single method.

The cast provides a simple API for creating and retrieving actors. Consider the partial NUnit test method below.

[Test, Screenplay]
public void John_should_be_able_to_buy_eggs(ICast cast)
{
  var john = cast.Get<John>();
  // Perform tasks with John here, along with assertions
}

As with the stage, described above, there is no need to keep track of which actors have already been used. If the Get method is used for the same actor more than once in a single test, subsequent calls will return the same actor object.

Additionally, you might notice that the cast in the example above was used with a persona to get the actor. When using personas with the cast, abilities may be provided to the actor via dependency injection, simplifying the process of creating the actor.