Switch branches/tags
Nothing to show
d5f6845 Jun 7, 2018
1 contributor

### Users who have contributed to this file

286 lines (222 sloc) 9.94 KB

# Serenity Screenplay with REST Exercises

These exercises demonstrate how to use Serenity Screenplay with a REST API. They start with an existing application, which you can find on the `master` branch. The solutions can be found on the `solutions` branch.

Before doing these exercises, make sure you have the server running. You can launch the server by typing the following command in the project root directory:

`mvn spring-boot:run -Ddata.source=DEV`

## Exercise 1 - View the overall profit

Add a new scenario to the `viewing_positions.feature` feature file:

```Scenario: Making profits on multiple shares
Given Sarah Smith is a registered trader
When Sarah has purchased 5 SNAP shares at \$100 each
And Sarah has purchased 10 IBM shares at \$50 each
Then she should have the following positions:
| securityCode | amount | totalValueInDollars | profit |
| CASH         | 0      | 0.00                | 0.00   |
| SNAP         | 5      | 1000.00             | 500.00 |
| IBM          | 10     | 600.00              | 100.00 |```

Now run the `ViewingPositions` test runner to check that it works.

Next we want to see the overall profit for the portfolio. Modify the scenario we just added so that it also checks the overall profit

```Scenario: Making profits on multiple shares
Given Sarah Smith is a registered trader
When Sarah has purchased 5 SNAP shares at \$100 each
And Sarah has purchased 10 IBM shares at \$50 each
Then she should have the following positions:
| securityCode | amount | totalValueInDollars | profit |
| CASH         | 0      | 0.00                | 0.00   |
| SNAP         | 5      | 1000.00             | 500.00 |
| IBM          | 10     | 600.00              | 100.00 |
And the overall profit should be \$600```

Add a step definition method for this last step in the `ViewingPositionsStepDefinitons` class:

```@And("^the overall profit should be \\\$(.*)\$")
public void theOverallProfitShouldBe(double expectedProfit) throws Throwable {
}```

To check the overall profits in a portfolio, we can use the `/portfolio/1{portfolioId}/profit` endpoint. Add this endpoint to the `BDDTraderEndPoints` enum:

`PortfolioProfit("/portfolio/{portfolioId}/profit")`

Implement the Step Definition so that the current actor sends a GET query to this endpoint:

```@And("^the overall profit should be \\\$(.*)\$")
public void theOverallProfitShouldBe(double expectedProfit) throws Throwable {
Integer portfolioId = theActorInTheSpotlight().recall("clientPortfolioId"); (1)

theActorInTheSpotlight().attemptsTo(
.with(request -> request.pathParam("portfolioId", portfolioId))
);

Double actualProfit = SerenityRest.lastResponse().as(Double.class); (3)

assertThat(actualProfit).isEqualTo(expectedProfit);}```
1. We stored the client’s portfolio id when they where registered

2. Perform a simple GET on the /portfolio/{portfolioId}/profit endpoint

3. Retrieve the response.

## Exercise 2 - Using a Question object

We can refactor this code to make it more reusable by using a Question class.

```public static Question<Double> overallProfitForPortfolioId(Long portfolioId) {
.withPathParameters("portfolioId", portfolioId)
.returning(response -> response.as(Double.class));
}```

Then refactor the step definition method to use this Question class with the `seeThat()` expression:

```@And("^the overall profit should be \\\$(.*)\$")
public void theOverallProfitShouldBe(double expectedProfit) throws Throwable {

Integer portfolioId = theActorInTheSpotlight().recall("clientPortfolioId");

theActorInTheSpotlight().should(
seeThat(ThePortfolio.overallProfitForPortfolioId(portfolioId), is(equalTo(expectedProfit)))
);
}```

## Exercise 3 - Variations on a theme

Add some additional scenarios to explore variations. Some possible scenarios can include the following:

```Scenario: Making losses a single share
Given Sarah Smith is a registered trader
When Sarah has purchased 2 SNAP shares at \$300 each
Then she should have the following positions:
| securityCode | amount | totalValueInDollars | profit  |
| CASH         | 40000  | 400.00              | 0.00    |
| SNAP         | 2      | 400.00              | -200.00 |

Scenario: Making profits and losses across multiple share
Given Sarah Smith is a registered trader
When Sarah has purchased 2 SNAP shares at \$300 each
And she has purchased 5 IBM shares at \$50 each
Then she should have the following positions:
| securityCode | amount | totalValueInDollars | profit  |
| CASH         | 15000  | 150.00              | 0.00    |
| SNAP         | 2      | 400.00              | -200.00 |
| IBM          | 5      | 300.00              | 50.00  |
And the overall profit should be \$-150.00```

## Exercise 4 - Transaction history

In this exercise, we will write some scenarios to test the portfolio transaction history.

The full transaction history for a portfolio can be seen in the "history" entry of the portfolio record. We can access this record at the `/client/{clientId}/portfolio` endpoint.

Create a new feature file called `transation_history.feature` in the `src/test/resources/features` folder.

```Feature: Transaction history
In order to understand why I have no money left
I want to see a historyu of all my transactions

Scenario: All transactions are recorded in the transaction history
When Tim has purchased 5 SNAP shares at \$100 each
Then his transaction history should be the following:
| securityCode | type    | amount | priceInCents | totalInCents |
| CASH         | Deposit | 100000 | 1            | 100000       |
| CASH         | Sell    | 50000  | 1            | 50000        |
| SNAP         | Buy     | 5      | 10000        | 50000        |```

Now create a test runner for this feature file:

```@RunWith(CucumberWithSerenity.class)
@CucumberOptions(
plugin = {"pretty"},
features = "src/test/resources/features/portfolios/transaction_history.feature"
)
public class TransactionHistory {}```

Next, create a new step definition class called `TransactionHistoryStepDefinitions` in the `stepdefinitions` package. This class will query the REST end point to retrieve the transaction history (a list of trades), and compare them with the expected history:

```@Then("^(?:his|her) transaction history should be the following:\$")
public void his_transaction_history_should_be_the_following(List<Trade> transactionHistory) throws Exception {

Client registeredClient = theActorInTheSpotlight().recall("registeredClient"); (1)

theActorInTheSpotlight().attemptsTo( (2)
.with(request -> request.pathParam("clientId", registeredClient.getId()))
);

assertThat(SerenityRest.lastResponse().statusCode()).isEqualTo(200); (3)

.jsonPath()

assertThat(actualTransactionHistory).usingElementComparatorIgnoringFields("id","timestamp")
.containsExactlyElementsOf(transactionHistory); (4)
}```
1. Fetch the client ID

2. Get the portfolio record from the REST end point

3. Ensure that the query worked

4. Compare the transaction lists, ignoring irrelevant fields

### Exercise 5 - refactoring tasks and questions

To make the code in this step definition more readable and more usable, let’s extract some tasks and questions.

Create a new `Task` class in the `tasks` package to fetch the transaction history for a given client:

```public class FetchTransactionHistory implements Task {

private final Long clientId;

public FetchTransactionHistory(Long clientId) {
this.clientId = clientId;
}

@Override
public <T extends Actor> void performAs(T actor) {

actor.attemptsTo(
.with(request -> request.pathParam("clientId", clientId))
);

assertThat(SerenityRest.lastResponse().statusCode()).isEqualTo(200);
}

public static FetchTransactionHistory forClient(Client client) {
return instrumented(FetchTransactionHistory.class, client.getId());
}
}```

Next, add a method to the `ThePortfolio` class to return a new Question. This Question will return the transaction history that was retrieved in the previous task:

```public static Question<List<Trade>> history() {
}```

Finally, update the test to use the new classes:

```@Then("^(?:his|her) transaction history should be the following:\$")
public void his_transaction_history_should_be_the_following(List<Trade> transactionHistory) throws Exception {

Client registeredClient = theActorInTheSpotlight().recall("registeredClient");

theActorInTheSpotlight().attemptsTo(
FetchTransactionHistory.forClient(registeredClient) (1)
);

theActorInTheSpotlight().should(
seeThat("the portfolio history is correctly retrieved",
ThePortfolio.history(), (2)
Serenity generates rich living documentation for REST API tests. Stop the server and run `mvn verify` from the command line. When the tests are finished, open the Serenity report in `target/site/serenity/index.html` and see how the tests are rendered.