Skip to content

Commit 0366fff

Browse files
authored
Merge pull request #100 from bkconrad/feature/integration-testing
Feature/integration testing
2 parents 3370233 + f8507b6 commit 0366fff

File tree

12 files changed

+311
-1
lines changed

12 files changed

+311
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
# Screeps Config
1818
screeps.json
1919

20+
# ScreepsServer data from integration tests
21+
/server
22+
2023
# Numerous always-ignore extensions
2124
*.diff
2225
*.err

docs/in-depth/testing.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Testing
2+
3+
Automated testing helps prevent regressions and reproduce complex failure
4+
scenarios for bug fixing or feature implementation. This project comes with
5+
support for both unit and integration testing with your Screeps code.
6+
7+
You can read more about [unit and integration testing on
8+
Wikipedia](https://en.wikipedia.org/wiki/Test-driven_development).
9+
10+
This documentation will cover the testing setup for those already familiar with
11+
the process of test driven design.
12+
13+
Tests are written via [Mocha](https://mochajs.org/) and executed as tests only
14+
if they include `.test.ts` in their filename. If you have written a test file
15+
but aren't seeing it executed, this is probably why. There are two separate test
16+
commands and configurations, as unit tests don't need the complete Screeps
17+
server run-time as integration tests do.
18+
19+
## Running Tests
20+
21+
The standard `npm test` will execute all unit and integration tests in sequence.
22+
This is helpful for CI/CD and pre-publish checks, however during active
23+
development it's better to run just a subset of interesting tests.
24+
25+
You can use `npm run test-unit` or `npm run test-integration` to run just one of
26+
the test suites. Additionally you can supply Mocha options to these test
27+
commands to further control the testing behavior. As an example, the following
28+
command will only execute integration tests with the word `memory` in their
29+
description:
30+
31+
```
32+
npm run test-integration -- -g memory
33+
```
34+
35+
Note that arguments after the initial `--` will be passed to `mocha` directly.
36+
37+
## Unit Testing
38+
39+
You can test code with simple run-time dependencies via the unit testing
40+
support. Since unit testing is much faster than integration testing by orders of
41+
magnitude, it is recommended to prefer unit tests wherever possible.
42+
43+
## Integration Testing
44+
45+
Integration testing is for code that depends heavily on having a full game
46+
environment. Integration tests are completely representative of the real game
47+
(in fact they run with an actual Screeps server). This comes at the cost of
48+
performance and very involved setup when creating specific scenarios.
49+
50+
Server testing support is implmented via
51+
[screeps-server-mockup](https://github.com/Hiryus/screeps-server-mockup). View
52+
this repository for more information on the API.
53+
54+
By default the test helper will create a "stub" world with a 3x3 grid of rooms
55+
with sources and controllers. Additionally it spawns a bot called "player"
56+
running the compiled main.js file from this repository.
57+
58+
It falls on the user to properly set up preconditions using the
59+
screeps-server-mockup API. Importantly, most methods exposed with this API are
60+
asynchronous, so using them requires frequent use of the `await` keyword to get
61+
a result and ensure order of execution. If you find that some of your
62+
preconditions don't seem to take effect, or that you receive a Promise object
63+
rather than an expected value, you're likely missing `await` on an API method.
64+
65+
Finally, please note that screeps-server-mockup, and this repo by extension,
66+
come with a specific screeps server version at any given time. It's possible
67+
that either your local package.json, or the screeps-server-mockup package itself
68+
are out of date and pulling in an older version of the [screeps
69+
server](https://github.com/screeps/screeps). If you notice that test environment
70+
behavior differs from the MMO server, ensure that all of these dependencies are
71+
correctly up to date.

package.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
"push-main": "rollup -c --environment DEST:main",
1111
"push-pserver": "rollup -c --environment DEST:pserver",
1212
"push-sim": "rollup -c --environment DEST:sim",
13-
"test": "echo \"Error: no test specified\" && exit 1",
13+
"test": "npm run test-unit && npm run test-integration",
14+
"test-unit": "rollup -c rollup.test-unit-config.js && mocha dist/test-unit.bundle.js",
15+
"test-integration": "npm run build && rollup -c rollup.test-integration-config.js && mocha dist/test-integration.bundle.js",
1416
"watch-main": "rollup -cw --environment DEST:main",
1517
"watch-pserver": "rollup -cw --environment DEST:pserver",
1618
"watch-sim": "rollup -cw --environment DEST:sim"
@@ -26,16 +28,30 @@
2628
},
2729
"homepage": "https://github.com/screepers/screeps-typescript-starter#readme",
2830
"devDependencies": {
31+
"@types/chai": "^4.1.6",
2932
"@types/lodash": "^3.10.1",
33+
"@types/mocha": "^5.2.5",
3034
"@types/node": "^10.5.5",
3135
"@types/screeps": "^2.4.0",
36+
"@types/sinon": "^5.0.5",
37+
"@types/sinon-chai": "^3.2.0",
38+
"chai": "^4.2.0",
39+
"lodash": "^4.17.11",
40+
"mocha": "^5.2.0",
3241
"prettier": "^1.14.0",
3342
"rollup": "^0.63.4",
43+
"rollup-plugin-buble": "^0.19.4",
3444
"rollup-plugin-clear": "^2.0.7",
3545
"rollup-plugin-commonjs": "^9.1.4",
46+
"rollup-plugin-multi-entry": "^2.0.2",
3647
"rollup-plugin-node-resolve": "^3.3.0",
48+
"rollup-plugin-nodent": "^0.2.2",
3749
"rollup-plugin-screeps": "^0.1.2",
3850
"rollup-plugin-typescript2": "^0.16.1",
51+
"screeps-server-mockup": "^1.4.3",
52+
"sinon": "^6.3.5",
53+
"sinon-chai": "^3.2.0",
54+
"ts-node": "^7.0.1",
3955
"tslint": "^5.9.1",
4056
"tslint-config-prettier": "^1.14.0",
4157
"tslint-plugin-prettier": "^1.3.0",

rollup.test-integration-config.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use strict";
2+
3+
import clear from "rollup-plugin-clear";
4+
import resolve from "rollup-plugin-node-resolve";
5+
import commonjs from "rollup-plugin-commonjs";
6+
import typescript from "rollup-plugin-typescript2";
7+
import buble from 'rollup-plugin-buble';
8+
import multiEntry from 'rollup-plugin-multi-entry';
9+
import nodent from 'rollup-plugin-nodent';
10+
11+
export default {
12+
input: 'test/integration/**/*.test.ts',
13+
output: {
14+
file: 'dist/test-integration.bundle.js',
15+
name: 'lib',
16+
sourcemap: true,
17+
format: 'iife',
18+
globals: {
19+
chai: 'chai',
20+
it: 'it',
21+
describe: 'describe'
22+
}
23+
},
24+
external: ['chai', 'it', 'describe'],
25+
plugins: [
26+
clear({ targets: ["dist/test.bundle.js"] }),
27+
resolve(),
28+
commonjs(),
29+
typescript({tsconfig: "./tsconfig.test-integration.json"}),
30+
nodent(),
31+
multiEntry(),
32+
buble()
33+
]
34+
}

rollup.test-unit-config.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use strict";
2+
3+
import clear from "rollup-plugin-clear";
4+
import resolve from "rollup-plugin-node-resolve";
5+
import commonjs from "rollup-plugin-commonjs";
6+
import typescript from "rollup-plugin-typescript2";
7+
import buble from 'rollup-plugin-buble';
8+
import multiEntry from 'rollup-plugin-multi-entry';
9+
10+
export default {
11+
input: 'test/unit/**/*.test.ts',
12+
output: {
13+
file: 'dist/test-unit.bundle.js',
14+
name: 'lib',
15+
sourcemap: true,
16+
format: 'iife',
17+
globals: {
18+
chai: 'chai',
19+
it: 'it',
20+
describe: 'describe'
21+
}
22+
},
23+
external: ['chai', 'it', 'describe'],
24+
plugins: [
25+
clear({ targets: ["dist/test.bundle.js"] }),
26+
resolve(),
27+
commonjs(),
28+
typescript({tsconfig: "./tsconfig.json"}),
29+
multiEntry(),
30+
buble()
31+
]
32+
}

test/integration/helper.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const { readFileSync } = require('fs');
2+
const _ = require('lodash');
3+
const { ScreepsServer, stdHooks } = require('screeps-server-mockup');
4+
const DIST_MAIN_JS = 'dist/main.js';
5+
6+
/*
7+
* Helper class for creating a ScreepsServer and resetting it between tests.
8+
* See https://github.com/Hiryus/screeps-server-mockup for instructions on
9+
* manipulating the terrain and game state.
10+
*/
11+
class IntegrationTestHelper {
12+
private _server;
13+
private _player;
14+
15+
get server() {
16+
return this._server;
17+
}
18+
19+
get player() {
20+
return this._player;
21+
}
22+
23+
async beforeEach() {
24+
this._server = new ScreepsServer();
25+
26+
// reset world but add invaders and source keepers bots
27+
await this._server.world.reset();
28+
29+
// create a stub world composed of 9 rooms with sources and controller
30+
await this._server.world.stubWorld();
31+
32+
// add a player with the built dist/main.js file
33+
const modules = {
34+
main: readFileSync(DIST_MAIN_JS).toString(),
35+
};
36+
this._player = await this._server.world.addBot({ username: 'player', room: 'W0N1', x: 15, y: 15, modules });
37+
38+
// Start server
39+
await this._server.start();
40+
}
41+
42+
async afterEach() {
43+
await this._server.stop();
44+
}
45+
}
46+
47+
beforeEach(async () => {
48+
await helper.beforeEach();
49+
});
50+
51+
afterEach(async () => {
52+
await helper.afterEach();
53+
});
54+
55+
before(() => {
56+
stdHooks.hookWrite();
57+
});
58+
59+
after(() => {
60+
process.exit();
61+
})
62+
63+
export const helper = new IntegrationTestHelper();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {assert} from "chai";
2+
import {helper} from "./helper";
3+
4+
describe("main", () => {
5+
it("runs a server and matches the game tick", async function () {
6+
for (let i = 1; i < 10; i += 1) {
7+
assert.equal(await helper.server.world.gameTime, i);
8+
await helper.server.tick();
9+
}
10+
});
11+
12+
it("writes and reads to memory", async function () {
13+
await helper.player.console(`Memory.foo = 'bar'`);
14+
await helper.server.tick();
15+
const memory = JSON.parse(await helper.player.memory);
16+
assert.equal(memory.foo, 'bar');
17+
});
18+
});

test/mocha.opts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--require test/setup-node.js
2+
--require ts-node/register
3+
--ui bdd
4+
5+
--reporter spec
6+
--bail
7+
--full-trace
8+
--watch-extensions tsx,ts
9+
--colors
10+
11+
--recursive
12+
--timeout 5000

test/setup-node.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//inject mocha globally to allow custom interface refer without direct import - bypass bundle issue
2+
global._ = require('lodash');
3+
global.mocha = require('mocha');
4+
global.chai = require('chai');
5+
global.sinon = require('sinon');
6+
global.chai.use(require('sinon-chai'));

test/unit/main.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {assert} from "chai";
2+
import {loop} from "../../src/main";
3+
import {Game, Memory} from "./mock"
4+
5+
describe("main", () => {
6+
before(() => {
7+
// runs before all test in this block
8+
});
9+
10+
beforeEach(() => {
11+
// runs before each test in this block
12+
// @ts-ignore : allow adding Game to global
13+
global.Game = _.clone(Game);
14+
// @ts-ignore : allow adding Memory to global
15+
global.Memory = _.clone(Memory);
16+
});
17+
18+
it("should export a loop function", () => {
19+
assert.isTrue(typeof loop === "function");
20+
});
21+
22+
it("should return void when called with no context", () => {
23+
assert.isUndefined(loop());
24+
});
25+
});

0 commit comments

Comments
 (0)