How to use Cypress in a JS Game

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:

cypress_barbarians.gif

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!

cypress_barbarians2.gif

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 ๐Ÿ–ฅ.

ย