How to use Cypress in a JS Game
Functional Testing beyond a Web App
Why functional testing was useful for my Game
Recently I published a very minimalist javascript game ๐ฎ in itch.io, as I explained in a previous post.
I have worked on this project during intermittent periods. At some point, I needed to do a huge refactor, because I started this project 4 years ago ๐ before ES6 was approved so I wasnโt really using any of the new ๐ useful features as modules or Functional Programming syntax...
For example, I needed to change a lot of code in order to be able to add transition animations for the AI turn ๐ค. That meant changing a sequential code into a wait and callbacks
type of code. I needed to update the unit tests, which were designed to test the old code ๐ต๐ฝ. While those tests were broken โ, I wouldnโt have a way to be sure that the game's main features still worked ๐๐ฟโโ๏ธ.
But with functional tests, I could cover my main features (gaining gold, combat, winning, losing...), treating the production code as a โblack boxโ โฌ and therefore having tests that are completely independent of any big refactors ๐ช. If you want to know more about functional tests, you can check my previous post about applying them in a Web App ๐.
General Tips
1- โ Divide your specs into two groups/folders: "Core" and "Not Core". That way you can check that the main App features are still working when using Cypress UI ๐ณ; which is faster than launching the whole test suite:
2- ๐ก While you develop new tests, use .only() and .skip(); they are quite useful and allow you to test a single test individually without having to comment out code or similar workarounds. By the way, Cypress documentation is quite easy to follow and the community is great too ๐.
3- ๐ณ Cypress allows you to easily set the state of the game to a point that you want to test, avoiding repetitive actions ๐ฅฑ such as playing through all the level 1 to make sure that you can win and get to level 2. In my case, I achieve that using URL parameters to select a level, disable animations, sound...
4- โ You can add your Jasmine.js unit tests into a Cypress spec too. Of course, you should run your unit tests more frequently and independently of the functional ones. But it's cool to visit your jasmine tests HTML page with Cypress before the "real" functional tests just to check that everything is green โ and good to go!
Issues I found
๐ค My game had some luck and randomness involved in the AI movements and the combat resolution. You cannot test a feature that you donโt know exactly what will output, but luckily cypress API offers some functions that give the flexibility to test results. That's not a proper solution; it feels like a workaround: tests should be strict, so Iโm planning to add a flag to disable randomness.
โ The game used window alerts for showing some messages instead of a custom modal. I ended up using my own modal always, but meanwhile, I was able to test that using this code:
cy.on('window:alert', stub);
click('#button').then(() => {
expect(stub.getCall(0)).to.be.calledWith("Hello World!");
}
And by the way, you can do something similar for window:confirm
!
- โ Do you need to check that a console.log was printed? I was able to test that using this simple utils functions:
const CONSOLE_LOG = "consoleLog";
export const havePrinted = (text) =>
cy.get(`@${CONSOLE_LOG}`).should("be.calledWith", text);
export const mockedConsole = {
onBeforeLoad(win) {
cy.stub(win.console, "log").as(CONSOLE_LOG);
},
};
Then, I used it on my tests like this:
import { mockedConsole, havePrinted } from "./utils/console";
// You need to mock the console before the first load
cy.visit("my.fake.url", mockedConsole);
// This will return true if there's a console.log printing 'Hello world!'
havePrinted("Hello world!);
Closing time
It's funny to use tools out of the context they were designed for. I'm not sure Cypress can be applied in a canvas JS game for example. For simple HTML tags ๐ท games, it works perfectly as it's not so different from your usual Web App ๐ฅ.