Please read this section at least one day prior to the seminar. This section outlines what you are expected to know, be able to do, or prepare in advance. Following these instructions will help you get the most out of the seminar and ensure smooth participation.
This is a remote practical. There is no on-site activity. Instead, you are expected to solve the assignments in the time and place of your choosing. Unless stated otherwise, you are expected to work alone. You are not allowed to share your solution with your colleagues. Each assignment has a specific deadline and submission conditions.
You are not allowed to collaborate with your colleagues.
Avoid over-reliance on AI tools for implementing this assignment.
You are required to finish all exercises, commit and push your solution, and deploy both backend and frontend before 2026-05-12 23:59 (UTC+1). Make sure to include the deployed URLs in your repository README.
This practical is designed as a series of exercises that build on each other. You are expected to solve the exercises in the given order and carry your source code forward. It is a good idea to commit after each exercise.
How do you know your software works?
./practical-06/
Vitest is a test framework with Vite-native support. Meaning, you can easily use it with Vite project.
The objective is to implement Vitest for unit testing.
Unit testing is the process where you test the smallest functional unit of code.
Vitest has an excellent Getting Started. The main points to follow are:
npm install -D vitest
Continue to the next slide once you are done >>>
// Vitest provides:
// - test executor : the vitest command
// - test definition : describe, test
// - test assertions : expect
import { describe, test, expect } from "vitest";
// We import what we aim to test.
// This should be only a single file for a unit tests.
import { createState } from "./application-state";
// We define a collection of test relevant for given function.
describe("createState", () => {
// This is the single test.
// The test function can be asynchronous (async).
test("Default test.", () => {
// Prepare input and initial state.
const search = "?data-source=1&submit-url=2";
// Compute actual state.
const actual = createState(search);
// Define and assert for expected state.
const expected = { dataSource: "1", submitUrl: "2", initialized: false, submitted: false };
// We use "toStrictEqual" to check for the internals of the object.
expect(actual).toStrictEqual(expected);
});
});
It is easier to write tests if you have properly utilized decompositions.
Continue to the next slide once you are done >>>
Your objective is to implement unit tests into your JavaScript user interface application. Work with the code from assignment 06. You are allowed to modify the code, but try to keep the changes to minimum.
You need to implement at least three tests to satisfy following:
Continue to the next slide once you are done >>>
The next step is to add a test coverage. This provides us with an estimate of how much code we test. Beware that test coverage should not be the main metric! Yet, when paired with additional rules it can be useful. E.g. 100% test coverage for controllers.
Computing test coverage with Vitest is easy. Output the tests as JSON and HTML using reporters. You can configure the reporters using "vite.config.ts" file.
Add a "coverage": "vitest run --coverage" command to "package.json" to run the tests and compute the coverage.
Continue to the next slide once you are done >>>
By default the coverage report is saved to "coverage" directory. As a result, you need to add "coverage" into your ".gitignore" file.
Congratulations, if you have followed the instructions, you have just reached the end of this exercise.
Run the tests automatically on every push.
./.gitlab-ci.yml
The objective is to implement a simple CI pipeline using GitLab. You should already know the basics from NSWI177.
You can read the .gitlab-ci.yml documentation.
We would like to interact with our application the same way as the user.
End-to-end (E2E) testing is a Software testing methodology to test a functional and data application flow consisting of several sub-systems working together from start to end.
End-to-end testing, also known as E2E testing, is an approach to testing that that simulates real user experiences to validate the complete system.
For the purpose of this practical we limit ourself only to simulating a user interaction with the dialog. We basically simulate user interaction with our application using a web browser.
Continue to the next slide once you are done >>>
In order to implement the tests we need to:
When it comes to the browser and its control, we can use Playwright, Puppeteer, Cypress, ...
For purpose of this practical we utilize Cypress as it has simple setup, intuitive API, built in assertions, automatic waiting, ...
Continue to the next slide once you are done >>>
./practical-06/
There is a Cypress Install guide. Please install Cypress using npm "npm install cypress --save-dev".
Cypress integrates a custom application which allow you to easily create and execute the test. Before opening the application you may need to modify your "tsconfig.json" and add following fragment.
"compilerOptions": {
"module": "ESNext"
},
Continue to the next slide once you are done >>>
Next open the application using "npx cypress open" and select "E2E testing". Confirm quick configuration and select a browser you would like to choose. From the perspective of the tutorial you should continue with Your First Test.
Select "Create new spec" and accept the default.
describe('template spec', () => {
it('passes', () => {
cy.visit('https://example.cypress.io')
})
})
Look around the application. Try to modify the source file, by default "spec.cy.js" using your IDE of choice. Reload and re-execute the test.
Continue to the next slide once you are done >>>
In order to run the test for our application we need to start it first. Start your application using "npm run dev" and update the test in "spec.cy.js" to load your application. Do not point Cypress to webik, instead point it to your local dev server.
You may get a "ECONNREFUSED" error. This meas that the browser is not able to access your application. You can test it manually by opening a new tab and navigating to URL of your application. Try to solve this on your own using any tools necessary. There is also a possible solution in this presentation source.
Continue to the next slide once you are done >>>
Cypress provide a rich API. While I do recommend you to take a explore the official documentation, here is a list of few of the basic one:
Continue to the next slide once you are done >>>
Here is a simple example using API from the previous slide.
// Intercept POST call to "http://example.com/data-submit", return "{}" as a response, and assign "postSubmitData" as an alias.
cy.intercept("POST", "http://example.com/data-submit", "{}")
.as("postSubmitData");
// Select by text and perform click operation.
cy.contains("Send").click();
// Wait for application to make the POST request and check the content.
cy.wait("@postSubmitData").then(interception => {
const request = JSON.parse(interception.request.body);
expect(request.title).to.be.equal("Instance");
});
Continue to the next slide once you are done >>>
"fixture" provide a way how to fetch data content from files in "./cypress/fixtures". You can pair it with intercept like this:
cy.intercept("GET", "data-source", { fixture: "data-source" })
.as("getDataSource");
This will load file "./cypress/fixtures/data-source.json".
Continue to the next slide once you are done >>>
Continue to the next slide once you are done >>>
Continue to the next slide once you are done >>>
Update your cypress test to test application running at URL as defined by APPLICATION_URL. Value of APPLICATION_URL can be specified as environment variable or using APPLICATION_URL property in .env file.
You can utilize cy.env to access environment variables from tests. You can load .env file to Cypress using following configuration file:
# Import package and load all variables .env into process.env object.
import dotenv from "dotenv";
dotenv.config();
import { defineConfig } from "cypress";
export default defineConfig({
env: { APPLICATION_URL: process.env.APPLICATION_URL }
});
Do not forget to install and save "dotenv" as a dependency.
Continue to the next slide once you are done >>>
Congratulations, if you have followed the instructions, you have just reached the end of this exercise.