I am working on a real-time multiplayer board game built using node and socket.io for the backend and react + redux for the frontend. It's the first time I'm doing a project of this sort, i.e. real-time and multiplayer.
I am unsure on how best to go about integration/system testing. How can I actually automate spinning up, say 10 frontends and having them play a game together? Should I use a testing framework for this and if so, which one would be a good choice and why?
I found this question with the same question. I have figured out a way for it to work, which I'm guessing you have as well by now, but for someone else to come across:
A clarification on terms: integration testing can be done for react with things like Jest + enzyme, using mount(). I'm answering this based on looking for end-to-end / acceptance testing, where you're essentially testing your product from the user's standpoint (here, navigating around on a website).
As this is from the user's perspective, I believe it is irrelevant that you're using React.
So, how to do this? There are many JS testing options. That resource can help understand which testing package you might want to select. You want something that simulates an actual browser.
In investigating some of the options listed in the above resource, I have found that:
edit: I initially proposed using nightmare. However, I was getting some wonky behavior when running multiple tests (unexpected timeouts, Electron instances not closing properly), and have investigated some other options. But I'll retain the information for reference:
I selected nightmare because it was advertised as simple.
Below is an example test, using Jest and nightmare (and some sloppy TypeScript). The site has a button to end the player's turn, and there is a header that indicates whose turn it is. I'll simulate clicking that button and making sure the header changes as expected. Also note that you'll need your dev server and frontend running during these tests.
import * as Nightmare from 'nightmare';
let nightmare1: Nightmare;
let nightmare2: Nightmare;
beforeEach(async () => {
nightmare1 = new Nightmare({ show: true })
nightmare2 = new Nightmare({ show: true })
await nightmare1
.goto('http://127.0.0.1:3000');
await nightmare2
.goto('http://127.0.0.1:3000');
});
afterEach(async () => {
await nightmare1.end();
await nightmare2.end();
});
it('sockets turn changes via End Turn button', async () => {
expect.assertions(6);
// Both display the same player's turn ("Red's Turn")
const startingTurnIndicator1 = await nightmare1
.evaluate(() => document.querySelector('h1').innerText);
const startingTurnIndicator2 = await nightmare2
.evaluate(() => document.querySelector('h1').innerText);
expect(startingTurnIndicator1).toBe(startingTurnIndicator2);
// Both change ("Blue's Turn")
const oneClickTI1 = await nightmare1
.click('button')
.evaluate(() => document.querySelector('h1').innerText)
const oneClickTI2 = await nightmare2
.evaluate(() => document.querySelector('h1').innerText);
expect(oneClickTI1).toBe(oneClickTI2);
expect(oneClickTI1).not.toBe(startingTurnIndicator1);
// Both change back ("Red's Turn")
const twoClickTI2 = await nightmare2
.click('button')
.evaluate(() => document.querySelector('h1').innerText)
const twoClickTI1 = await nightmare1
.evaluate(() => document.querySelector('h1').innerText);
expect(twoClickTI1).toBe(twoClickTI2);
expect(twoClickTI1).toBe(startingTurnIndicator2);
expect(twoClickTI1).not.toBe(oneClickTI1);
});
I'm not sure how good the actual code in this test is, but it works.