Lecture #14. VueJS Web app testing (1)
Lecture #14. VueJS Web app testing (1)
Лабораторна робота №1
Лабораторна робота №2
Лабораторна робота №3
2
Manual vs. automated testing
Acceptance tests are formal tests executed to verify if a system satisfies its
business requirements. They require the entire application to be up and
running and focus on replicating user behaviors. But they can also go further
and measure the performance of the system and reject changes if certain goals
are not met.
Differences between Acceptance
and E2E Testing
1. Purpose / Goal
Acceptance Tests check if the system satisfies business or customer requirements.
E2E Tests check if all parts of the system work together technically.
2. Perspective
Acceptance Testing is from the business or user perspective (e.g., "Can a user buy a product?").
E2E Testing is from the technical user flow perspective (e.g., "Does the 'Buy' button work end-
to-end?")
3. Ownership
Acceptance Tests are often defined by Product Owners, Business Analysts, or QA, possibly
involving non-technical stakeholders.
E2E Tests are usually written by developers or QA engineers using automated tools.
4. Automation: Acceptance tests may be manual or automated. E2E tests are mostly
automated.
10
5. Timing: Acceptance often happens near release ("final check before delivery"). E2E tests run
UI/UX tests
UI UX
● Visual inconsistencies
● the order placement process must be
Smoke tests are basic tests that check basic functionality of the application.
They are meant to be quick to execute, and their goal is to give you the assurance
that the major features of the system are working as expected.
Smoke tests can be useful right after a new build is made to decide whether or
not you can run more expensive tests, or right after a deployment to make
sure that they application is running properly in the newly deployed
environment.
Planning, Design, and
Development
During this phase, you create an Automation strategy & plan, which contains the
following details-
Unit tests help to fix bugs early in the development cycle and save costs.
It helps the developers to understand the testing code base and enables
them to make changes quickly
Good unit tests serve as project documentation
Unit tests help with code re-use. Migrate both your code and your tests to your
new project. Tweak the code until the tests run again.
Unit Testing Best Practices
<script>
export default {
data: function() {
return {
msg: "Hello"
};
}
};
</script>
First unit test
- create a new folder called tests/unit in your root directory
- create a new file called App.test.spec.js
App.test.js
import App from '../src/App.vue'
import { mount } from '@vue/test-utils';
describe('Testing App component', () => {
it('checks textcontent to Hello ', () => {
const wrapper = mount(App); //returns instance of the component.
//accessing dom element and checks textcontent
expect(wrapper.element.textContent).toBe('Hello');
})
})
Vitest
VitestSince the official setup created by create-vue is based on Vite,
we recommend using a unit testing framework that can leverage
the same configuration and transform pipeline directly from Vite.
For Behavioral logic: assert correct render updates or emitted events in response to
user input events.
Using vitest and vue-test-utils
together
// Import Vitest tools
import { describe, it, expect } from 'vitest';
// Import Vue Test Utils
import { mount } from '@vue/test-utils';
// Import your component
import MyButton from '@/components/MyButton.vue';
describe('MyButton.vue', () => {
it('renders the correct text when clicked', async () => {
const wrapper = mount(MyButton);
await wrapper.trigger('click');
expect(wrapper.text()).toContain('Clicked');
});
});
26
Cypress: End to End Testing
End to End Testing, or UI testing is one the many approaches for testing a web
application.
An end to end test checks whether a web application works as expected or
not, by testing the so called user flow.
Is End to End Testing important? Yes it is. But, nobody likes E2E tests. They can
be slow, cumbersome and expensive to write.
Testing Your First Application (the course from the authors)
Example
Features
Time Travel: Cypress takes snapshots as your tests run. Hover over commands in the Command
Log to see exactly what happened at each step.
Debuggability: Stop guessing why your tests are failing. Debug directly from familiar tools like
Developer Tools. Our readable errors and stack traces make debugging lightning fast.
Automatic Waiting: Never add waits or sleeps to your tests. Cypress automatically waits for
commands and assertions before moving on. No more async hell.
Spies, Stubs, and Clocks: Verify and control the behavior of functions, server responses, or timers.
The same functionality you love from unit testing is right at your fingertips.
Network Traffic Control: Easily control, stub, and test edge cases without involving your server.
You can stub network traffic however you like.
Consistent Results: Our architecture doesn’t use Selenium or WebDriver. Say hello to fast,
consistent and reliable tests that are flake-free.
Screenshots and Videos: View screenshots taken automatically on failure, or videos of your entire
test suite when run from the CLI.
Cross browser Testing: Run tests within Firefox and Chrome-family browsers (including Edge and
Electron) locally and optimally in a Continuous Integration pipeline.
Running simple E2E Test
Default Test.js:
// https://docs.cypress.io/api/introduction/api.html
cy.get('#hide-textbox').click()
cy.get('#displayed-text').should('not.be.visible')
cy.get('#show-textbox').click()
cy.get('#displayed-text').should('be.visible')
Window/Browser Interaction
// Simulate Back and Forward in browser
cy.go(‘back’)
cy.go(‘forward’)
// Navigate to URL
cy.visit(‘https://www.google.co.uk’)
// Refresh the page
cy.reload()
// Scroll the window
//x and y coordinates to scroll to
cy.scrollTo(x, y)
// Clear cookies
cy.clearCookies()
// Take a screenshot, full page or of an element
cy.screenshot()
Interacting with the DOM
// Click an element
cy.get(‘button’).contains(‘Login’).click()
cy.get(‘button’).contains(‘Login’).dblclick()
cy.get(‘button’).contains(‘Login’).rightclick()
// Select an option from a dropdown
cy.get(‘dropdownselector’).select('user-1')
// Trigger an event
cy.get('alink').trigger('mousedown')
Interacting with the DOM
Continued
// Enter or clear text in an input or text field
cy.get(‘textfield’).type(‘stringtotype’)
cy.get(‘textfield’).clear()
// Interact with a checkbox
cy.get(‘checkboxToUse’).check()
cy.get(‘checkboxToUse’).uncheck()
// Focus on an element in the DOM
cy.get(‘textfield’).focus()
// Blur a focused element
cy.get(‘textfield’).blur()
// Force an action even if element may not be actionable
cy.get('button').click({ force: true })
Then
38
Introduction to TypeScript
Boolean
The most basic datatype is the simple true/false value, which JavaScript and
TypeScript call a boolean value.
let isDone: boolean = false;
Types in TypeScript
Number
As in JavaScript, all numbers in TypeScript are either floating point values or
BigIntegers. These floating point numbers get the type number, while
BigIntegers get the type bigint. In addition to hexadecimal and decimal literals,
TypeScript also supports binary and octal literals introduced in ECMAScript 2015.
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
Types in TypeScript
String
Just like JavaScript, TypeScript also uses double quotes (") or single quotes (') to
surround string data.
let color: string = "blue";
color = 'red';
We can also use template strings, which can span multiple lines and have
embedded expressions. These strings are surrounded by the backtick/backquote
(`) character, and embedded expressions are of the form ${ expr }.
Types in TypeScript
Array
TypeScript, like JavaScript, allows you to work with arrays of values. Array types
can be written in one of two ways. In the first, you use the type of the elements
followed by [] to denote an array of that element type:
let list: number[] = [1, 2, 3];
The second way uses a generic array type, Array<elemType>:
let list: Array<number> = [1, 2, 3];
Types in TypeScript
Tuple
Tuple types allow you to express an array with a fixed number of elements whose types are
known, but need not be the same.
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error
Types in TypeScript
When accessing an element with a known index, the correct type is retrieved:
console.log(x[0].substring(1)); // OK
console.log(x[1].substring(1)); // error:
Property 'substring' does not exist on type 'number'.
x[3] = "world";
Tuple type '[string, number]' of length '2' has no element at index '3'.
Types in TypeScript
Enum
A helpful addition to the standard set of datatypes from JavaScript is the enum. As in languages like
C#, an enum is a way of giving more friendly names to sets of numeric values.
enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;
By default, enums begin numbering their members starting at 0.
Types in TypeScript
Unknown
Values may come from dynamic content – e.g. from the user – or we may want to intentionally
accept all values in our API. In these cases, we want to provide a type that tells the compiler and
future readers that this variable could be anything, so we give it the unknown type.
let notSure: unknown = 4;
notSure = "maybe a string instead";
// OK, definitely a boolean
notSure = false;
declare const maybe: unknown;
// 'maybe' could be a string, object,
boolean, undefined, or other types
const aNumber: number = maybe;
Type 'unknown' is not assignable to type
'number'.
Types in TypeScript
Any
In some situations, not all type information is available or its declaration would take an inappropriate
amount of effort. These may occur for values from code that has been written without TypeScript or
a 3rd party library. In these cases, we might want to opt-out of type checking. To do so, we label
these values with the any type:
declare function getValue(key: string): any;
// OK, return value of 'getValue' is not checked
const str: string = getValue("myString");
Unlike unknown, variables of type any allow you to access arbitrary properties, even ones that
don’t exist.
TypeScript 3.0 introduces a new top type unknown. unknown is the type-safe counterpart of any.
Anything is assignable to unknown, but unknown isn’t assignable to anything but itself
and any without a type assertion or a control flow based narrowing. Likewise, no operations are
permitted on an unknown without first asserting or narrowing to a more specific type.
Types in TypeScript
Void
void is a little like the opposite of any: the absence of having any type at all. You may
commonly see this as the return type of functions that do not return a value:
function warnUser(): void {
console.log("This is my warning message");
}
Declaring variables of type void is not useful because you can only assign null (only if --
strictNullChecks is not specified, see next section) or undefined to them:
let unusable: void = undefined;
// OK if `--strictNullChecks` is not given
unusable = null;
Types in TypeScript
With object type, APIs like Object.create can be better represented. For example:
// OK
create({ prop: 0 });
create(null);
create("string"); //error
TypeScript functions
When you call the add() function, the TypeScript compiler will check each
argument passed to the function to ensure that they are numbers.
TypeScript functions
TypeScript compiler will match the number of parameters with their types and the return type.
The following example shows how to assign a function to the add variable:
In TypeScript, the compiler checks every function call and issues an error in the
following cases:
● The number of arguments is different from the number of parameters
specified in the function.
● Or the types of arguments are not compatible with the types of function
parameters.
TypeScript Optional Parameters
function name(parameter1=defaultValue1,...) {
// do something
}
In this syntax, if you don’t pass arguments or pass the undefined into the
function when calling it, the function will take the default initialized values for
the omitted parameters. For example:
function applyDiscount(price, discount = 0.05) {
return price * (1 - discount);
}
console.log(applyDiscount(100)); // 95
TypeScript function overloadings
class Person {
firstName: string;
lastName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
let person = new Person('John', 'Smith');
TypeScript Access Modifiers
Access modifiers change the visibility of the properties and methods of a class.
TypeScript provides three access modifiers:
private
protected
public
Note that TypeScript controls the access logically during compilation time,
not at runtime.
TypeScript Access Modifiers
class Person {
getFullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
TypeScript readonly
TypeScript provides the readonly modifier that allows you to mark the properties of a class
immutable. The assignment to a readonly property can only occur in one of two places:
class Person {
readonly birthDate: Date;
constructor(birthDate: Date) {
this.birthDate = birthDate;
}
}
Readonly vs. const
readonly
Use for Class properties Variables
const
Initialization In the declaration or in the constructor of the same class
In the declaration
TypeScript inheritance
class Employee {
static headcount: number = 0;
An abstract class is typically used to define common behaviors for derived classes
to extend.
Unlike a regular class, an abstract class cannot be instantiated directly.
abstract class Employee {
constructor(private firstName: string, private lastName: string) {
}
abstract getSalary(): number
}
TypeScript abstract classes
The following uses an interface called Person that has two string properties:
interface Person {
firstName: string;
lastName: string;
}
By convention, the interface names are in the camel case. They use a single
capitalized letter to separate words in there names. For example, Person,
UserProfile, and FullName.
TypeScript interfaces
interface Person {
firstName: string;
middleName?: string;
lastName: string;
}
Function types
To describe a function type, we assign the interface to the function signature that contains the
parameter list with types and returned types. For example:
interface StringFormat {
(str: string, isUpper: boolean): string
}
console.log(format('hi', true)); // HI
Class Types
For example, the following Json interface can be implemented by any unrelated
classes:
interface Json {
toJSON(): string
}
class Person implements Json {
constructor(private firstName: string,
private lastName: string) {
}
toJson(): string {
return JSON.stringify(this);
}
}
Interfaces extending
interface Mailable {
send(email: string): boolean
queue(email: string): boolean
}
interface FutureMailable extends Mailable {
later(email: string, after: number): boolean
}
An interface can extend multiple interfaces, creating a combination of all the interfaces.
TypeScript Generics
TypeScript generics allow to write the reusable and generalized form of functions, classes, and
interfaces. In this tutorial, you’re focusing on developing generic functions.
console.log(getRandomAnyElement(numbers));
console.log(getRandomAnyElement(colors));
Typescript Singleton
class MySingleton { const myInstance1: MySingleton =
MySingleton.getInstance();
static instance: MySingleton; const myInstance2: MySingleton =
static a: number = 0; MySingleton.getInstance();
private constructor() {
const myInstance3: MySingleton =
MySingleton.getInstance();
console.log("constructor called!");
} const myInstance4: MySingleton =
MySingleton.getInstance();
public static getInstance(): MySingleton
MySingleton.logic(); // 4
{
MySingleton.a++; A = new MySingleton()
if (!MySingleton.instance) {
MySingleton.instance = new
MySingleton();
}
//The VehicleHandler is "abstract" because noone
Typescript Factory is going to instantiate it
//We want to extend it and implement the
interface Vehicle { abstract method
move(): void abstract class VehicleHandler {
}
//This is the method real handlers need to
//The classes we care about, the "move" method implement
is where our "business logic" would live public abstract createVehicle(): Vehicle
class Car implements Vehicle {
//This is the method we care about, the rest of
public move(): void { the business logic resides here
console.log("Moving the car!") public moveVehicle(): void {
} const myVehicle = this.createVehicle()
} myVehicle.move()
}
class Bicycle implements Vehicle { }
public addObserver(o:Observer):void {
this.observers.push(o)
}
protected notify() {