A mock port server for testing HTTP requests.
1.1K
A mock port server for testing HTTP requests.
There are two ways to use this project
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.
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"
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
| Name | Default | Purpose |
|---|---|---|
MOCKFILE | ./data/mock.json | Path to the mockfile to serve |
PORT | 1234 | Port the server listens on |
For example, to serve the animal mocks on port 4321:
MOCKFILE=./data/animal.json PORT=4321 npm start
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.HEAD route is registered automatically for every non-HEAD mock, so curl -I reports a sensible status.response.status is omitted, a per-method default is used: GET/HEAD → 200, POST → 201, PUT/PATCH/DELETE → 204./pets/4 entry in data/mock.json demonstrates.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.
| File | Mocks | Used 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.
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.
A Makefile wraps the whole workflow. It is the quickest way to drive the
project without memorising docker flags:
make help
| Target | What it does |
|---|---|
make install | npm ci from the lockfile |
make test | Run the unit tests |
make coverage | Run the tests with coverage (fails below 100%) |
make audit | Fail on known vulnerabilities, as CI does |
make check | coverage + audit |
make start | Run the server locally |
make start-animal | Run locally against data/animal.json |
make build | Build the docker image |
make run | Build, then run the container detached |
make logs | Follow the container's console output |
make stop / make rm | Stop / remove the container |
make restart | Recreate the container from the current image |
make ps | Show the container's status |
make smoke | Build the image and prove it serves a mock |
make ci | Everything CI runs, locally |
make release | Open a PR bumping the version (see Releasing) |
make tag | Tag the merged bump, ready to push |
make clean / make distclean | Remove 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:
| Variable | Default | Purpose |
|---|---|---|
IMAGE | mitchallen/mockport-server | Image name |
TAG | latest | Image tag |
CONTAINER | mockport-server | Container name |
HOST_PORT | 7777 | Host port make run publishes |
PORT | 1234 | Port inside the container |
MOUNT | $(PWD)/test/data | Host dir mounted over /usr/src/app/data |
MOCKFILE | unset | Mockfile for make start |
BUMP | patch | Version 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.
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.)
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.
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.
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.
There are two ways to run the container:
You will need to change the port in the examples echoed to the docker console.
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
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
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
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
docker ps
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
docker stop mockport-server
docker start mockport-server
docker stop mockport-server
docker rm mockport-server
docker stop mockport-server
docker rm mockport-server
docker rmi mitchallen/mockport-server
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
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.
The v* tag runs the tests once at the tagged tree, then in parallel:
| Job | Result |
|---|---|
publish-ghcr | linux/amd64 + linux/arm64 images to ghcr.io/mitchallen/mockport-server |
publish-dockerhub | the same images to mitchallen/mockport-server, then syncs this README to the Docker Hub description |
release | a 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
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:
| Name | Kind | Value |
|---|---|---|
DOCKERHUB_TOKEN | secret | A Docker Hub access token with Read/Write/Delete scope |
DOCKERHUB_USERNAME | variable | Docker 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.
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
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.
Content type
Image
Digest
sha256:4e7148713…
Size
59.9 MB
Last updated
6 days ago
docker pull mitchallen/mockport-server