Testing and validating Web Applications
Sylvain Fargier (BE-CEM-MRO)
01/06/21
Introduction
- This presentation aims to:
- Present a set of tools and methodology
- Open discussion on test-driven-development for web-applications
- It is not:
- A recommendation or guideline document
- A document to debate on technologies
Introduction: forewords
Introduction: General Overview
-
Web-Application
- Dynamic web-page that interacts with a middle-ware
- Similar to a regular application
-
Web-Site
- Static web-page
- May interact with a middleware (ex: database)
- Does not display "live" values
Introduction: Web-Application
Back-end tests
Back-end tests: framework
BDD like console output
describe('KnexREST tests', function() {
it('can list databases', async function() {
/* ... */
});
});

source: mrest.js (BE-CEM-MRO)
-
Chai.js assertion library
- BDD/TDD oriented : expect, assert, should
- `expect` syntax eases the code lecture
Back-end tests: assertions


diff error details
/*...*/
expect(ret).to.deep.include({ status: 200, body: [
{ id: 1, name: 'toto', value: 42 },
{ id: 2, name: 'titi', value: 43 },
{ id: 3, name: 'titi', value: 43 }
] });source: mrest.js (BE-CEM-MRO)
-
Sinon.js spying, mocking and stubbing library
- Can replace, spy or mock anything
- Using sandbox to easily cleanup
Back-end tests: mocks & stubs

sinon.spy(DnsClient.prototype, 'query');
return q()
.then(() => DicRpc.invoke(/*...*/))
.then(
() => { throw 'should fail'; },
(err) => {
expect(err).to.be.instanceOf(NotFound);
expect(DnsClient.prototype.query.callCount)
.to.equal(1, 'should query the DNS only once');
})
.finally(() => DnsClient.prototype.query.restore());ex: checks that a deeply nested object is used only once, source: dim.js (BE-CEM-MRO)
-
Sinon.js is heavily artilery
- Used really seldomly
- Hand-written stubs are more effective
- Use SQlite when a DB is needed, ex: mrest.js
- Implement both server and client part, ex: dim.js, cmw-core.js
- Stub complete devices/middleware, ex: ntof-stubs
- Those piece of codes are intensively re-used (for both back-end and front-end tests) !
Back-end tests: mocks & stubs

- Linters: Eslint, JSHint
- TypeChecker: TypeScript
- Using JSDoc comments (Doxygen like)
- Code-coverage: nyc (istanbul)
-
GitLab-CI integration:
- Test results and reports are processed by GitLab
- Using an helper library (BE-CEM-MRO) to simplify the setup
- Still exposing complete reports
Back-end tests: static analysis


source: coverage report
Front-end tests
-
Browser code (ECMAScript, CSS, HTML)
- A library: only front-end code
- Or an application: Node.js back-end + front-end
- Bundled using WebPack
- Transpiled with Babel
- Running in "moving" environment
- Must be tested ! tested again ! and again !
Front-end tests: Introduction
Front-end tests: Introduction
-
Karma test runner
- Supports main browsers (Chrome, Firefox, Safari ...)
- Headless mode
- Integrates with WebPack, Mocha, nyc ...
Front-end tests: test runner

asciinema record : base-vue (BE-CEM-MRO) tests in Headless Firefox (speed x2.5)
-
Karma debugging
- Watch mode for TDD/BDD (fasten bundling)
- Real browser connection (console, analysis ...)
Front-end tests: test runner


mocha report with user control over tests
Chrome running ssvg-engine (BE-CEM-MRO) test suite
-
Karma.js integration
- Using an helper library (BE-CEM-MRO)
- In GitLab-CI using helper scripts (BE-CEM-MRO)
- For Web-Applications, front-end and back-end test results are merged (code-coverage, results, static-analysis ...)
Front-end tests: test runner
const
{ karmaConfig } = require('karma-mocha-webpack');
module.exports = function(karma) {
karma.set(karmaConfig(karma));
};base-vue (BE-CEM-MRO) configuration using karma-mocha-webpack (BE-CEM-MRO)
-
Vue-test-utils unit testing utility library
- Utilities to manipulate DOM and trigger events
-
Some home made utilities
- to wait for application states (ex: wait for animation to settle, or asynchronous work to finish)
Front-end tests: user interactions
it('can use a dialog', async function() {
wrapper = mount(BaseDialog, { propsData: { title: 'test dialog' } });
let prom = wrapper.vm.request();
let button = await waitForWrapper(wrapper,
() => wrapper.findAll('button').filter((b) => b.text() === 'Ok'));
button.trigger('click');
expect(await prom).to.equal(true);
/*...*/
});base-vue (BE-CEM-MRO) Dialog testing
-
karma-server-side
- Run code on the karma server side (Node.js)
- Re-use stubs and back-end code we saw earlier (now it all makes sense 🎉)
- Use Mocha beforeEach/afterEach to setup a custom middleware for each test
Front-end tests: middleware interactions
await server.run(function() {
const { EACSStub } = serverRequire('ntof-stubs');
this.env.stub.eacs.state.setState(EACSStub.EACSState.State.RUNNING);
});n_ToF operation application (op-control)
-
So far we've focused on unit-tests
- Represents about 80% of bugs (totally arbitrary number)
-
For the remaining bugs: continuous delivery
- Deploy early, test often, give some attention to your work !
- Be thankful to IT for the awesome infrastructure they provide !
Front-end tests: continuous integration
Front-end tests: continuous integration
BE-CEM-MRO deployment workflow for web-apps
Questions ?
Testing and validating Web Applications
By Sylvain fargier
Testing and validating Web Applications
- 237