Spaces:
Running
Running
Pokemon Showdown uses mocha for its unit tests. To run, use `npm test` on the command line. Certain tests run more slowly (marked as `(slow)` in the test's title); you can use `npm run full-test` for those as well, but it will take longer and usually isn't necessary for most bugfixes. When submitting a pull request, GitHub Actions will automatically run full-test. | |
To run specific tests, you can change `it` or `describe` to `it.only` or `describe.only`. `npx mocha -g "text"` can let you run tests with "text" in its title (e.g. `npx mocha -g "Gen 1"` for all tests with the string "Gen 1"). | |
## Creating tests | |
Check the test/ directory to see if a file already exists for the effect that you're writing a test. If not, create the file with the following boiler plate: | |
'use strict'; | |
const assert = require('./../../assert'); | |
const common = require('./../../common'); | |
let battle; | |
describe(`Name of effect you're testing`, function () { | |
afterEach(function () { | |
battle.destroy(); | |
}); | |
it(`test should start here`, function () { | |
}); | |
}); | |
To create the battle, use common.createBattle and pass in two arrays of teams. You can add additional flags as well: | |
- gameType: 'doubles', 'triples', 'multi', 'freeforall', etc (tests default to singles) | |
- forceRandomChance: true, false (any RNG calls that use Battle#randomChance will default to true or false instead of calling RNG) | |
- Examples include accuracy, critical hits, secondary effects, being fully paralyzed, Abilities like Flame Body, and successive Protects. | |
- It won't control things like random targeting, sampling from a list (e.g. Metronome), or rolling variable durations (e.g. number of sleep turns). | |
- seed: Array of 4 ints (used to force a specific RNG when the default seed isn't compatible. Avoid relying on RNG if possible) | |
- other custom rules, e.g. Inverse Mod: defined in test/common.js | |
Here are two examples of tests in the preferred style. Don't hesitate to copy tests similar to what you're trying to do! | |
``` | |
it(`should restore 1/3 HP to the user after eating a Berry`, function () { | |
battle = common.createBattle([[ | |
{species: 'wynaut', item: 'lumberry', ability: 'cheekpouch', moves: ['sleeptalk']}, | |
], [ | |
{species: 'pichu', moves: ['nuzzle']}, | |
]]); | |
const wynaut = battle.p1.active[0]; | |
battle.makeChoices(); | |
assert.fullHP(wynaut); | |
}); | |
``` | |
``` | |
it(`should boost Dondozo's stat even if Sheer Force-boosted`, function () { | |
battle = common.createBattle({gameType: 'doubles'}, [[ | |
{species: 'wynaut', moves: ['sleeptalk']}, | |
{species: 'mew', ability: 'shellarmor', moves: ['sleeptalk']}, | |
], [ | |
{species: 'tatsugiristretchy', ability: 'commander', moves: ['sleeptalk']}, | |
{species: 'dondozo', ability: 'sheerforce', moves: ['orderup']}, | |
]]); | |
battle.makeChoices('auto', 'move orderup 2'); | |
const mew = battle.p1.active[1]; | |
const damage = mew.maxhp - mew.hp; | |
assert.bounded(damage, [149, 176], `Order Up's base power should be increased by Sheer Force`); | |
assert.statStage(battle.p2.active[1], 'spe', 3); | |
}); | |
``` | |
Tests ideally should be: | |
- Specific. Don't cram too much into one unit test. Don't include Abilities/moves/items that aren't necessary for the test. | |
- Readable. If a test fails from some regression, it should not take very long to identify what the test was doing. Making liberal use of comments and assert() descriptions is great! | |
- RNG-independent (where possible). Use 100% accurate moves (or No Guard), use Shell Armor or Lucky Chant, don't use Pokemon that Speed tie, things like that. | |
You may see tests not in the preferred style, or that aren't ideal in other ways. Pokemon Showdown has thousands of unit tests, and it's unfortunately pretty time-consuming to convert them all. Please be sure to follow the preferred style! | |
## Using assert | |
``assert`` is used to validate some condition in your test is true. See test/assert.js for a complete list. Use ``assert.false`` to check the inverse of that condition. | |
Common functions: | |
- `assert.equal(oneThing, anotherThing)`: use this instead of `assert.equals()` or `assert(oneThing === anotherThing)` | |
- `assert.fullHP(pokemon)`: check that the Pokemon is at full HP | |
- `assert.bounded(damage, [lowerBound, upperBound])`: check if the damage dealt by a move falls within the possible range of damage | |
- `assert.statStage(pokemon, 'stat', value)`: check the stat stage of the current Pokemon | |
### Other tips | |
Here are some helpful tips for writing and debugging tests: | |
- `console.log(battle.getDebugLog())` is a helpful tool to quickly debug a test. It prints the entire battle log with omniscient details about the game. | |
- To save a replay for viewing, you can also do `common.saveReplay(battle)` and check the test/replays directory if you prefer to view it in replay form (e.g. to check client behavior). Open the html file it creates in any standard web browser to view. | |