Sign inSign up

mitchallen/mockport-server

By mitchallen

•Updated 6 days ago

A mock port server for testing HTTP requests.

Image
0

1.1K

mitchallen/mockport-server repository overview

⁠mockport-server

A mock port server for testing HTTP requests.

CI coverage Docker image version Docker image size Docker pulls Node.js License: MIT

⁠Usage

There are two ways to use this project

  1. Run locally
  2. Run as a docker container

⁠Running Locally

Requires Node.js 20 or later.

npm install
npm start

This will echo the default mocks requests as curl commands.

There is also a Makefile covering the whole workflow — make start, make test, make run and so on. Run make help for the list, or see Make targets⁠.

⁠To test locally

Cut and paste some of the sample curl commands from the console into another terminal window.

For example, to issue one of the GET requests:

curl "http://localhost:1234/pets/1"
⁠Test locally with a different mockfile

The mockfile is chosen with the MOCKFILE environment variable, which defaults to ./data/mock.json. The repo ships a second mockfile, data/animal.json, and a script that uses it:

npm run start:animal

which is the same as:

MOCKFILE=./data/animal.json node src/index.js
⁠Environment variables
NameDefaultPurpose
MOCKFILE./data/mock.jsonPath to the mockfile to serve
PORT1234Port the server listens on

For example, to serve the animal mocks on port 4321:

MOCKFILE=./data/animal.json PORT=4321 npm start

⁠Mock file format

A mockfile is a JSON array of request/response pairs:

[
    {
        "request": {
            "method": "GET",
            "url": "/pets/1"
        },
        "response": {
            "status": 200,
            "body": { "id": 1, "name": "Pepper" }
        }
    }
]

Notes on matching and defaults:

  • request.url is matched exactly, including any query string. /api/login?foo=bar will not match a mock registered as /api/login.
  • A HEAD route is registered automatically for every non-HEAD mock, so curl -I reports a sensible status.
  • If response.status is omitted, a per-method default is used: GET/HEAD → 200, POST → 201, PUT/PATCH/DELETE → 204.
  • A 204 status never returns a body, even if one is defined — that is what the /pets/4 entry in data/mock.json demonstrates.
  • Anything unmatched returns 404.
  • Every request is echoed to the console — method, host, url, path, and any query string, body, or headers — so you can see exactly what the client under test sent.
  • CORS is enabled for all origins, so a browser-based client can call the mock server directly.
⁠Trying a mock with curl

That /pets/1 entry is the first mock in data/mock.json, so with the server running (npm start, or make start) it answers on port 1234. Piping the response through jq⁠ pretty-prints it:

curl -s http://localhost:1234/pets/1 | jq
{
  "id": 1,
  "name": "Pepper"
}

-s silences curl's progress meter, which would otherwise be interleaved with the body and confuse jq.

Mocks are matched on method and url only, so a POST gets back whatever the mockfile says regardless of what you send — the request body is echoed to the server console rather than used for matching:

curl -s -X POST http://localhost:1234/pets \
    -H 'Content-Type: application/json' \
    -d '{"name":"Bluey"}' | jq
{
  "id": 5,
  "name": "Bluey"
}

Error mocks come back the same way:

curl -s -X PATCH http://localhost:1234/pets/3 | jq
{
  "error": "Forbidden - you are not authorized"
}

Since jq only ever sees the body, use -w when the status code is the point — a 204 or a bare response.status mock has no body to print:

curl -s -o /dev/null -w '%{http_code}\n' -X DELETE http://localhost:1234/pets/5
204

Or -i to see the headers and the body together.

⁠Mockfiles in the repo
FileMocksUsed by
data/mock.json/pets/*npm start, and the image's built-in default
data/animal.json/animals/*npm run start:animal
test/data/mock.json/dogs/*the sample volume mount in the docker examples below

All three are exercised by the test suite, so they cannot drift out of sync with the server.


⁠Development

Install dependencies and run the unit tests:

npm install
npm test

With coverage:

npm run test:coverage

Coverage must stay at 100% (statements, branches, functions and lines); test:coverage fails below that. The coverage badge above is static and backed by that threshold: if coverage drops, CI (and its badge) goes red.

CI (.github/workflows/ci.yml) runs on every push to main and every pull request. It runs the tests with coverage on Node 22 and 24 (writing the coverage table to the job summary and uploading the report as the coverage artifact), fails the build on an npm audit finding of moderate or higher, and builds the Docker image and curls a mock out of the running container.

⁠Make targets

A Makefile wraps the whole workflow. It is the quickest way to drive the project without memorising docker flags:

make help
TargetWhat it does
make installnpm ci from the lockfile
make testRun the unit tests
make coverageRun the tests with coverage (fails below 100%)
make auditFail on known vulnerabilities, as CI does
make checkcoverage + audit
make startRun the server locally
make start-animalRun locally against data/animal.json
make buildBuild the docker image
make runBuild, then run the container detached
make logsFollow the container's console output
make stop / make rmStop / remove the container
make restartRecreate the container from the current image
make psShow the container's status
make smokeBuild the image and prove it serves a mock
make ciEverything CI runs, locally
make releaseOpen a PR bumping the version (see Releasing⁠)
make tagTag the merged bump, ready to push
make clean / make distcleanRemove build output / also node_modules and the image

Targets that need dependencies install them first, so make test works from a fresh clone. The install is stamped against the lockfile, so it only re-runs when package.json or package-lock.json actually changes.

Anything worth changing is a variable:

VariableDefaultPurpose
IMAGEmitchallen/mockport-serverImage name
TAGlatestImage tag
CONTAINERmockport-serverContainer name
HOST_PORT7777Host port make run publishes
PORT1234Port inside the container
MOUNT$(PWD)/test/dataHost dir mounted over /usr/src/app/data
MOCKFILEunsetMockfile for make start
BUMPpatchVersion increment make release applies

For example:

make run HOST_PORT=8080
make run MOUNT=                       # use the image's built-in mockfile
make start MOCKFILE=./data/animal.json

make smoke mirrors the docker job in CI: it builds the image, starts a container on its own name and port so it never disturbs one left running by make run, waits for the server, curls a mock, and tears the container down whether or not the check passed. It follows MOUNT, so it curls /dogs/1 against the mounted mockfile and /pets/1 against the built-in one.


⁠Running as a Docker Container

The published image is built for linux/amd64 and linux/arm64, runs as the unprivileged node user, and declares a HEALTHCHECK — so docker ps reports the container as healthy once the server answers. (The healthcheck hits an unmocked path and accepts the 404: any HTTP response means the server is up.)

⁠Use a different mockfile

By default the server will use its internal file:

/usr/src/app/data/mock.json

The run command below shows how to map that folder to a local folder called test/data.

Before running the container, create test/data in your current folder.

Create the file test/data/mock.json

Run the container and the mocks should be picked up from your file.

See the example in the repo for what a mock.json file should look like.

Note that the copy in this repo, test/data/mock.json, mocks /dogs/* rather than the /pets/* of the built-in file — that is how the examples below make it obvious which mockfile the container actually picked up.


⁠Pull the image from the repo

Every release is published to two registries. Either works — pick one:

docker pull ghcr.io/mitchallen/mockport-server:latest
docker pull mitchallen/mockport-server:latest

The examples below use the shorter Docker Hub name. Prefix it with ghcr.io/ to run the GHCR copy instead; the images are identical.

⁠Build the image locally
make build

or, equivalently:

npm run docker:build

The raw docker commands in the sections below spell out what is happening. make build, make run, make stop, make rm and make restart do the same things with the ports and paths already filled in — see Make targets⁠.

⁠Run the image locally as a container

There are two ways to run the container:

  • In the background, using the -d (detached) flag
  • Or in the foreground without it, to monitor console output.

You will need to change the port in the examples echoed to the docker console.


⁠Run in the background

This example runs the server locally on port 7777 in the background.

docker run -d -p 7777:1234 -v ${PWD}/test/data:/usr/src/app/data --name mockport-server mitchallen/mockport-server

⁠Run in the foreground

This example runs the server locally on port 7777 in the foreground.

It removes the -d flag to monitor the console.

docker run -p 7777:1234 -v ${PWD}/test/data:/usr/src/app/data --name mockport-server mitchallen/mockport-server

⁠Reattach to a container

Unless you remove the container you can't run it again.

You have to use the start command.

Use -a flag to attach to the console to monitor output

docker start -a mockport-server

⁠Rerun with the same or a new container
docker stop mockport-server
docker rm mockport-server
docker run -d -p 7777:1234 -v ${PWD}/test/data:/usr/src/app/data --name mockport-server mitchallen/mockport-server

⁠Confirm the image is running
docker ps

⁠Test with curl commands

Assumes the container is running and mapped to port 7777.

With the test/data volume mounted, as in the run examples above:

curl http://localhost:7777/dogs/1

Without a volume mount the container serves its built-in data/mock.json:

curl http://localhost:7777/pets/1

⁠Start and stop a running container
docker stop mockport-server

docker start mockport-server

⁠Remove
⁠Remove Container
docker stop mockport-server
docker rm mockport-server
⁠Remove Image
docker stop mockport-server
docker rm mockport-server
docker rmi mitchallen/mockport-server

⁠Mock two containers

This example runs the two servers on ports 7001 and 7002.

docker run -p 7001:1234 -v ${PWD}/test/data/srv1:/usr/src/app/data --name mock1 mitchallen/mockport-server

Open another terminal window to monitor the second container.

docker run -p 7002:1234 -v ${PWD}/test/data/srv2:/usr/src/app/data --name mock2 mitchallen/mockport-server

They will look for and use these two files on your host machine:

${PWD}/test/data/srv1/mock.json
${PWD}/test/data/srv2/mock.json

⁠Releasing

Earlier versions of this project were built automatically by Docker Cloud, which Docker shut down in 2021. Releasing now runs in GitHub Actions (.github/workflows/publish.yml), triggered by a version tag. Released versions are listed on the Releases page⁠ — the version numbers below are only examples.

A release is three commands. First, open a PR that bumps the version:

make release

That bumps package.json and the lockfile (patch by default — pass BUMP=minor, BUMP=major, or an exact VERSION=0.2.0), commits the bump on a release-x.y.z branch, pushes it and opens the PR with gh. The bump goes through a PR like anything else, so CI verifies it before it lands.

Once that PR merges, tag the merge commit and push the tag:

git checkout main && git pull
make tag VERSION=0.1.2
git push origin v0.1.2

The tag must stay in step with the version in package.json — the server echoes that version on startup, so a mismatch ships an image that misreports itself. make tag refuses to tag unless package.json already says 0.1.2. It creates the tag but does not push it, since pushing is what triggers the publish — that stays a deliberate, separate step.

⁠What a tag push does

The v* tag runs the tests once at the tagged tree, then in parallel:

JobResult
publish-ghcrlinux/amd64 + linux/arm64 images to ghcr.io/mitchallen/mockport-server
publish-dockerhubthe same images to mitchallen/mockport-server, then syncs this README to the Docker Hub description
releasea GitHub Release for the tag, with notes generated from the merged PRs

Both registries get three tags: the full version, the major.minor (0.1), and latest. The release job runs last, so a Release only exists for a tag whose images actually shipped.

Pull from either registry:

docker pull ghcr.io/mitchallen/mockport-server:latest
docker pull mitchallen/mockport-server:latest
⁠One-time setup

GHCR needs no configuration — it authenticates with the built-in GITHUB_TOKEN. The package inherited the repository's public visibility on its first publish, so anonymous docker pull works. If a future package ever lands private, flip it under Packages > Package settings > Change visibility.

Docker Hub needs credentials, under Settings > Secrets and variables > Actions:

NameKindValue
DOCKERHUB_TOKENsecretA Docker Hub access token with Read/Write/Delete scope
DOCKERHUB_USERNAMEvariableDocker Hub account (optional, defaults to mitchallen)

The Delete scope looks heavier than it needs to be, and nothing here deletes anything. Pushing images alone would be happy with Read/Write, but the description sync uses an endpoint that rejects a read/write token with 403 Forbidden — which is how v0.1.2 failed. Tokens are managed at app.docker.com⁠; an existing token's scope can be widened in place, leaving the secret's value unchanged.

A narrower token is not fatal: the sync step is continue-on-error, so images and the Release still go out and the description just stays as it was. That shows up as a green step whose log says Forbidden rather than Request successful, so check the log, not the check mark.

Until DOCKERHUB_TOKEN is set that job skips with a notice instead of failing, so tagging a release will not produce a red build — and GHCR still publishes.

To check the credentials without publishing, run the workflow by hand from the Actions tab (or gh workflow run publish.yml). A manual run is a dry run — it logs in and builds both architectures for both registries, but pushes nothing and creates no Release. Only a v* tag publishes.

⁠Publishing by hand
make build
docker tag mitchallen/mockport-server mitchallen/mockport-server:0.1.2
docker push mitchallen/mockport-server:0.1.2
docker push mitchallen/mockport-server:latest

Docker Hub page for this image

Docker Hub page for this image's tags


⁠License

MIT — see LICENSE⁠.

The license covers this project's own code. It does not apply to any third party assets that were imported into the project as a utility or for demonstration purposes; contact the authors of those assets for their licensing information.

Tag summary

Content type

Image

Digest

sha256:4e7148713…

Size

59.9 MB

Last updated

6 days ago

docker pull mitchallen/mockport-server