Write Automated Tests for Electron With Spectron, Mocha, and Chai
Automated testing is a key part of any truly Agile environment. We take a look at how to use these technologies to achieve testing excellence.
Join the DZone community and get the full member experience.
Join For FreeIn this article, you will learn how to test your Electron-based desktop application with Mocha, Chai, and Spectron. You will learn how to set up the test environment and run automated integration or E2E tests with Spectron, Mocha, and Chai. Furthermore, a short introduction to CSS selectors is given.
1. Cross-Platform Apps With Electron
Electron allows us to write cross-platform apps for Windows, Linux, and Mac based on the same code base. Thereby, we develop our desktop applications using HTML, CSS, and Javascript (or Typescript). Finally, we package our Electron app with electron-packager or electron-builder and ship it as OS-specific bundle (.app, .exe, etc.). The application itself runs on Chromium. In this article, I will give an introduction on how to setup the test environment for automated end-to-end test or integration tests for your Electron application.
2. Automated Test Environment
Mocha is a JavaScript test framework which runs on Node.js. Mocha allows us to use any assertion library we like. In the following, I will use Chai with should
-style assertions.
Chai is a BDD/TDD assertion library for Node.js. Furthermore, we will use Chai as Promised which extends Chai for asserting facts about promises. Thereby, we can transform any Chai assertion into one that acts on a promise.
Spectron is an open source framework for easily writing automated integrations and end-to-end tests for Electron. It provides capabilities for navigating on web pages, simulating user input, and much more. It also simplifies the setup, running, and tearing down of the test environment for Electron.
Through Spectron, you have the entire Electron API and Chromium API available within your tests. Thereby, we can test our Electron apps using ChromeDriver and WebdriverIO.
Finally, we can also run the tests on continuous integration services and build servers, such as Travis and Jenkins.
3. Environment Setup
First, we install Spectron via npm as development dependency:
npm install --save-dev spectron
You may want to install chai and chai-as-promised as well.
npm install --save-dev chai
npm install --save-dev chai-as-promised
Chai is a BDD/TDD assertion library for node and Chai-as-promised extends it with assertions about promises.
Also install @types/mocha, electron-chromedriver, mocha, and spectron as dev dependencies.
Finally, your devDependencies in your package.json should look like this (perhaps with newer version numbers):
"devDependencies": {
"@types/mocha": "2.2.43",
"chai": "4.1.2",
"chai-as-promised": "7.1.1",
"electron-chromedriver": "1.7.1",
"mocha": "3.5.3",
"spectron": "3.7.2"
}
Setting Up the Mocha Test Runner
Next, we go to our package.json file to set up the test commands. A minimal configuration calls the Mocha test runner without any parameter settings.
"scripts": {
"test": "mocha"
}
When we execute npm run test, Mocha is executed looking for files defining tests (can be a unit test or an integration test).
The following command tells Mocha to only run tests contained at a specific location. The grep command tells it to only consider files with a certain file name. The --watch
parameter runs tests on changes to JavaScript files in the defined directory and once initially.
mocha {{folder/with/tests}} --grep {{^regex$}} --watch
I have created two scripts. One is for running all integration tests in the test folder. The other one runs only one specific test every time the test or the class under test is changed.
test:all
runs Mocha, with the following conditions:
- With mocha-jenkins-reporter as the reporter.
- Sets the timeout to 20 seconds.
- Tells Mocha to only look for tests in the src/test folder within files whose names start with test- and end with .js
"test:all": "mocha -R mocha-jenkins-reporter --timeout 20000 \"./src/test/**/*test-*.js\""
test:one
tells Mocha to watch for code changes and to re-run the test. We use grep to run only tests where the name fits the given string.
"test:one": "mocha --watch -u bdd \"./src/test/**/test-*.js\" --grep \"test-login-form\""
Setting Up Spectron
I have written an extra class where I have a function to launch Spectron which then creates a new Application
class that, when configured, starts and stops our Electron application via app.start()
and app.stop()
.
Therefore, we have to specify the path to the electron batch file as well as to our dist folder. Here, I am setting the start timeout to 20 seconds. By setting the property chromeDriverLogPath, Chromedriver is activated to execute logging.
initialiseSpectron() {
let electronPath = path.join(__dirname, "../../node_modules", ".bin", "electron");
const appPath = path.join(__dirname, "../../dist");
if (process.platform === "win32") {
electronPath += ".cmd";
}
return new Application({
path: electronPath,
args: [appPath],
env: {
ELECTRON_ENABLE_LOGGING: true,
ELECTRON_ENABLE_STACK_DUMPING: true,
NODE_ENV: "development"
},
startTimeout: 20000,
chromeDriverLogPath: '../chromedriverlog.txt'
});
}
Have a look in the application API of Spectron to find out more about the configuration.
4. Writing Automated Tests
After setting up our test environment, we will write an integration test using Mocha and Chai. I am going to write a test for a login form written in Angular 4 using reactive forms which I have presented in this article.
We create a new JavaScript class in our test folder which we call test-login-form.js. First, we import all libraries and call our Spectron helper class.
const testhelper = require("./spectron-helper");
const app = testhelper.initialiseSpectron();
const chaiAsPromised = require("chai-as-promised");
const chai = require("chai");
chai.should();
chai.use(chaiAsPromised);
Here, I am using the should
style which allows me to chain assertions by starting with the should
property. You can also use the expect or the assert style.
Life Cycle Hooks
Similar to JUnit, Mocha offers functions for initial setup, cleaning, and tear down actions. These life cycle hooks are called before(), beforeEach(), after(), and afterEach().
We use the before hook to set up Chai-as-promised and to start our Electron application via Spectron.
before(function () {
chaiAsPromised.transferPromiseness = app.transferPromiseness;
return app.start();
});
The after
function is used to tear down everything and to stop the application.
after(function () {
if (app && app.isRunning()) {
return app.stop();
}
});
Defining Test Beds and Tests
We define a test called Login via the following:
describe("Login", function () {
it('open window', function () {
}
});
Initially, I would test if the application was successfully initialized. Thereby, a window should be created. We test this via:
it('open window', function () {
return app.client.waitUntilWindowLoaded().getWindowCount().should.eventually.equal(1);
});
Remember, app is our Electron application. Client is the renderer process. This simple example also shows how Chai-as-promised can be used with Spectron. By appending should.eventually.equal(1) we create a promise which is resolved when a window is loaded before the default timeout of five seconds is over. Otherwise, the promise is rejected and the test fails.
Simulate Actions and Select Elements With CSS Selectors
We can also simulate clicks and the filling out of forms.
it("go to login view", function () {
return app.client.element('#sidebar').element("a*=credentials").click();
});
This test searches for a DOM element with the ID #sidebar. Within this element, it looks for another element which matches the CSS selector a*=credentials. Finally, we simulate a click on this element (in this case a link to another view).
For a reference on CSS selectors, I would recommend having a look at this and this page.
You can simulate a click on a button <button>Store login credentials</button> whose text includes Store by using click and the *-selector. Use = to search for an exact match.
click('button*=Store')
For more complex selections, you can also chain elements. The next example searches for elements within the form which is of type input with the formcontrolName username.
element('form input[formControlName="username"]');
To check if the username field is initially empty and has focus, we execute this test:
it("test initial form", function () {
return app.client
.getText(usernameInput).should.eventually.equal('')
.hasFocus(usernameInput);
}
Wrap Up
I hope that this article gave you a good first impression on how you to set up your own test environment for your Electron application with Spectron, Mocha, and Chai. You can find the full test class and a video showing the automated test in action on my website.
Published at DZone with permission of Dr. Matthias Sommer. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments