Step CIを利用して、API Testingを自動生成する

# Step CIを利用して、API Testingを自動生成する

> Open-source API Test Automation framework
> Security. Performance. Load Testing

[Step CI](https://docs.stepci.com/)は、自動API TestのOpen Source Libraryです。
workflow.ymlに記載したテストケースを利用して、自動API Testを実施していきます。
Test Generator(Importing)などですでにある、[OpenAPI](https://docs.stepci.com/import/openapi.html), [Postman](https://github.com/stepci/stepci/issues/29), [Insomnia](https://github.com/stepci/stepci/issues/30)からテストコードの自動生成もすることができます。

個人的には、[jest](https://docs.stepci.com/integration/jest.html)などのIntegrationとして扱ったり、QAチームの人と一緒に対応していくと、コードの資産化がしやすくなります。
[Playwright](https://playwright.dev/)とも併用が可能なので、Playwrightがおすすめです。

# 0. before do

## 0-0. Project Structure

想定しているディレクトリ構成とファイルの中身

```terminal
tree . -I node_modules
.
├── README.md
├── compose.yaml
├── dockerfile
├── openapi3
│   ├── generated
│   │   └── @typespec
│   │       └── openapi3
│   │           └── openapi.yaml
├── package-lock.json
├── package.json
```

<details>
<summary>compose.yaml</summary>

```compose.yaml
services:
  http_schema_container:
    container_name: http_schema_container
    build:
      context: .
      dockerfile: dockerfile
    tty: true
    restart: always
    volumes:
      - .:/usr/src/app
```

</details>

<details>
<summary>dockerfile</summary>

```dockerfile
FROM node:23-alpine3.19

WORKDIR /usr/src/app
```

</details>

<details>
<summary>openapi3/generated/@typespec/openapi3/openapi.yaml</summary>

```openapi3/generated/@typespec/openapi3/openapi.yaml
openapi: 3.0.0
info:
  title: (title)
  version: 0.0.0
tags: []
paths:
  /pets:
    get:
      operationId: Pets_list
      parameters:
        - name: filter
          in: query
          required: true
          schema:
            type: string
          explode: false
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Pet'
    post:
      operationId: Pets_create
      parameters: []
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
  /pets/{id}:
    get:
      operationId: Pets_read
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
  /stores:
    get:
      operationId: Stores_list
      parameters:
        - name: filter
          in: query
          required: true
          schema:
            type: string
          explode: false
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Store'
  /stores/{id}:
    get:
      operationId: Stores_read
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The request has succeeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Store'
components:
  schemas:
    Address:
      type: object
      required:
        - street
        - city
      properties:
        street:
          type: string
        city:
          type: string
    Pet:
      type: object
      required:
        - name
        - age
      properties:
        name:
          type: string
        age:
          type: integer
          format: int32
    Store:
      type: object
      required:
        - name
        - address
      properties:
        name:
          type: string
        address:
          $ref: '#/components/schemas/Address'

```

</details>


## 0-1. typespec を利用して、OpenAPIのコードを生成する


```terminal
tree . -I node_modules
.
├── README.md
├── compose.yaml
├── dockerfile
├── openapi3
│   ├── generated
│   │   └── @typespec
│   │       └── openapi3
│   │           └── openapi.yaml
│   ├── main.tsp
│   └── tspconfig.yaml
├── package-lock.json
├── package.json
```

<details>
<summary>openapi3/main.tsp</summary>

```openapi3/main.tsp
import "@typespec/http";

using TypeSpec.Http;

model Pet {
  name: string;
  age: int32;
}

model Store {
  name: string;
  address: Address;
}

model Address {
  street: string;
  city: string;
}

@route("/pets")
interface Pets {
  list(@query filter: string): Pet[];
  create(@body pet: Pet): Pet;
  read(@path id: string): Pet;
}

@route("/stores")
interface Stores {
  list(@query filter: string): Store[];
  read(@path id: string): Store;
}

```

</details>

<details>
<summary>openapi3/tspconfig.yaml</summary>

```openapi3/tspconfig.yaml
warn-as-error: true                           # Treat warnings as errors
output-dir: "{cwd}/openapi3/generated"       # Configure the base output directory for all emitters
emit:
  - "@typespec/openapi3"
```

</details>

# 1. Step CIの依存関係を設定する

```terminal
% npm install stepci
```

```package.json
"scripts": {
    "cli:stepci": "stepci"
}
```

# 2. OpenAPIファイルから、StepCIのAuto Testを追加する

```package.json
"scripts": {
    "gen:test": "stepci generate ./openapi3/generated/@typespec/openapi3/openapi.yaml ./test/gen/workflow.yml",
}
```

```terminal
% mkdir -p test/gen
% npm run gen:test
> app@0.1.0 gen:test
> stepci generate ./openapi3/generated/@typespec/openapi3/openapi.yaml ./workflow.yml
```

```terminal
tree . -I node_modules
.
├── README.md
├── compose.yaml
├── dockerfile
├── openapi3
│   ├── generated
│   │   └── @typespec
│   │       └── openapi3
│   │           └── openapi.yaml
│   ├── main.tsp
│   └── tspconfig.yaml
├── package-lock.json
├── package.json
└── test
    └── gen
        └── workflow.yml
```

<details>
<summary>test/gen/workflow.yml</summary>

```test/gen/workflow.yml
version: "1.0"
name: (title)
config:
  http: {}
tests:
  default:
    name: Default
    steps:
      - id: Pets_list
        http:
          url: /pets
          method: GET
          params:
            filter: sunt ex
          check:
            status: 200
            schema:
              type: array
              items:
                $ref: "#/components/schemas/Pet"
      - id: Pets_create
        http:
          url: /pets
          method: POST
          headers:
            Content-Type: application/json
            accept: application/json
          json:
            name: irure ex
            age: -971456
          check:
            status: 200
            schema:
              $ref: "#/components/schemas/Pet"
      - id: Pets_read
        http:
          url: /pets/aliqua Excepteur
          method: GET
          check:
            status: 200
            schema:
              $ref: "#/components/schemas/Pet"
      - id: Stores_list
        http:
          url: /stores
          method: GET
          params:
            filter: dolore consectetur
          check:
            status: 200
            schema:
              type: array
              items:
                $ref: "#/components/schemas/Store"
      - id: Stores_read
        http:
          url: /stores/sed dolor non consectetur incididunt
          method: GET
          check:
            status: 200
            schema:
              $ref: "#/components/schemas/Store"
components:
  schemas:
    Address:
      type: object
      required:
        - street
        - city
      properties:
        street:
          type: string
        city:
          type: string
    Pet:
      type: object
      required:
        - name
        - age
      properties:
        name:
          type: string
        age:
          type: integer
          format: int32
    Store:
      type: object
      required:
        - name
        - address
      properties:
        name:
          type: string
        address:
          $ref: "#/components/schemas/Address"

```

</details>