Cases

Real-world APIs need powerful test automation. Ply cases deliver ultimate control by enabling you to run your requests programmatically using TypeScript.

Case suites are signified by applying the @suite decorator to a TypeScript class:

@suite('movie-crud')
export class MovieCrud {

Within a suite, test methods are marked by the @test decorator:

  @test('add new movie')
  async createMovie(values: any) {

The name parameter is optional in both @suite and @test. If not specified, this defaults to class/method name.

When a case suite is executed hereā€™s what happens:

Letā€™s walk through ply-demoā€™s movieCrud.ply.ts suite to further understand how Ply cases relate to requests. MovieCrudā€™s constructor loads a request suite by calling loadSuiteSync:

  constructor() {
    this.requestSuite = ply.loadSuiteSync('test/ply/requests/movies-api.ply.yaml');
  }

Our first test method, createMovie() runs a request and captures the result:

  @test('add new movie')
  async createMovie(values: any) {
      const result = await this.requestSuite.run('createMovie', values);
      assert.strictEqual(result.response?.status?.code, 201);
      assert.exists(result.response?.body);
      // capture movie id from response -- used in downstream values
      this.movieId = result.response?.body?.id;
      this.requestSuite.log.info(`Created movie: id=${this.movieId}`);
  }

Here weā€™re using Chai assertions to programmatically verify response status (technically unnecessary since status code is included in Plyā€™s results comparison). The main point is to capture movieId from the response body so that we can use it in subsequent tests.

The next test method, updateRating() runs the ā€˜updateMovieā€™ PATCH request and then a GET to verify the update:

  @test('update rating')
  async updateRating(values: any) {
      // update movie rating -- using id returned from createMovie request
      values.id = this.movieId;
      values.rating = 4.5;
      await this.requestSuite.run('updateMovie', values);
      // confirm the update
      await this.requestSuite.run('retrieveMovie', values);
  }

Notice that values are passed into our test method, and that weā€™re updating them programmatically to set id to what we previously captured, and rating to 4.5. These are used by ā€˜updateMovieā€™ in the movies-api.ply.yaml request suite weā€™ve loaded:

updateMovie: # PATCH
  url: '${baseUrl}/movies/${id}'
  method: PATCH
  headers:
    Accept: application/json
    Content-Type: application/json
  body: |-
    {
      "rating": ${rating}
    }

You might ask how we can ā€œconfirm the updateā€ in updateRating() above simply by running the ā€˜retrieveMovieā€™ request. Remember: all requests/responses are appended to the result file for verification once the case suite is finished.

Lastly we clean up after ourselves by deleting the movie weā€™ve created:

  @test('remove movie')
  async deleteMovie(values: any) {
      // delete movie
      await this.requestSuite.run('deleteMovie', values);
      // confirm the delete
      await this.requestSuite.run('retrieveMovie', values);
  }

The ā€˜deleteMovieā€™ request needs movie id in its values, but we donā€™t need to set it here because Ply preserves the updates we applied previously in our updateRating() test method.

@before/@after

Methods decorated with @before are invoked by Ply prior to calling test methods. Here weā€™re cleaning up any movie left over from previous tests that failed to complete:

  @before
  async beforeAll(values: any) {
      const deleteMovie = this.requestSuite.get('deleteMovie');
      assert.exists(deleteMovie);
      const response = await deleteMovie!.submit({...values, id: '435b30ad'});
      this.requestSuite.log.info('Cleanup response status code', response.status.code);
      // response status should either be 200 or 404 (we don't care which during cleanup)
      assert.ok(response.status.code === 200 || response.status.code === 404);
  }

By default, with no parameters to @before, our decorated method is called once before any tests are run. To designate that it should be called before each test, weā€™d decorate with @before('*'). The parameter is a glob pattern indicating which test(s) the method should be called before. The @after decoration behaves similarly.

Another thing to note in beforeAll() is that weā€™re calling submit() on the request instead of run() through the suite. This way we avoid including cleanup requests/responses in our results.

Running

Like requests, cases can be run via Ply CLI, or within Visual Studio Code by installing the Ply Extension.

ply-movies

Unlike previous exercises in this guide, our movie-crud case suite performs updates and changes data. This is not permitted on the movies API hosted at ply-ct.org. Luckily itā€™s easy to run ply-movies locally. First, change the valuesFiles section in plyconfig.yaml to use localhost:

valuesFiles:
  - test/values/global.json
  # - test/values/ply-ct.json
  - test/values/localhost.json

Hereā€™s how to start/stop ply-movies server as a background process through npm scripts:

cd ply-demo
npm run start-movies
# <run some tests>
npm run stop-movies

You could also choose to install ply-movies globally, and start the server in a separate command window:

npm install -g @ply-ct/ply-movies
ply-movies start

In that case youā€™d shut it down using ctrl-c once youā€™re done testing.

CLI execution

The command to run our movie-crud case suite is like this:

ply test/cases/movieCrud.ply.ts

And voila:

Running 'add new movie'
Cleanup response status code: 404
Request 'createMovie' submitted at 9/26/2020, 13:32:22:135
Movie created with id: 435b30ad
Created movie: id=435b30ad
Case 'add new movie' PASSED in 106 ms
Running 'update rating'
Request 'updateMovie' submitted at 9/26/2020, 13:32:22:184
Request 'retrieveMovie' submitted at 9/26/2020, 13:32:22:193
Case 'update rating' PASSED in 21 ms
Running 'remove movie'
Request 'deleteMovie' submitted at 9/26/2020, 13:32:22:204
Request 'retrieveMovie' submitted at 9/26/2020, 13:32:22:212
Case 'remove movie' PASSED in 15 ms

Overall Results: {"Passed":3,"Failed":0,"Errored":0,"Pending":0,"Submitted":0}
Overall Time: 1689 ms

Take a look at actual result file test/results/actual/cases/movie-crud.yaml to see how it includes all requests/responses for the entire suite.

Skipped requests

The requests in movies-api.ply.yaml are meant to be used by our movie-crud case. We donā€™t want these to be run as standalone requests when we run all tests by executing simply ply from the command line (or running all in VS Code). To avoid running these requests directly, we mark them as skipped in plyconfig.yaml:

  skip: requests/movies-api.ply.yaml

Next Topic: Postman