Code kann automatisch getestet werden, um sicherzustellen, dass er wie erwartet funktioniert
Popularität:
Wir werden eine Funktion schreiben und testen, die einen String auf eine vorgegebene Länge kürzt:
shorten('loremipsum', 6);
// should return 'lor...'
Implementierung, die getestet werden soll:
/**
* shortens a given string to a specified length,
* adding "..." at the end if it was shortened
*/
function shorten(text, maxlength) {
if (text.length > maxlength) {
return text.slice(0, maxlength - 3) + '...';
}
return text;
}
export default shorten;
einfache Tests:
// shorten.test.js
import assert from 'assert/strict';
import shorten from './shorten';
assert.equal(shorten('loremipsum', 4), 'l...');
assert.equal(shorten('loremipsum', 9), 'loremi...');
assert.equal(shorten('loremipsum', 10), 'loremipsum');
assert.equal(shorten('loremipsum', 11), 'loremipsum');
assert.equal
wirf eine Exception, wenn die Bedingung nicht erfüllt wird
Assertions können auf verschiedene Arten geschrieben werden:
assert:
assert.equal(a, b);
expect (Beispiel aus Jest):
expect(a).toEqual(b);
should (Beispiel aus Cypress):
inputField.should('have.value', '');
assert (node):
assert.equal(a, b);
assert.deepEqual(a, b);
assert.throws(() => JSON.parse(''));
// ...
expect(a).to.equal(4);
expect(a).not.to.equal(2);
expect(a).to.be.greaterThan(3);
expect(a).to.be.a('number');
expect(() => JSON.parse('')).to.throw();
expect(a).toEqual(4);
expect(a).not.toEqual(2);
expect(a).toBe(4);
expect(a).toBeGreaterThan(3);
expect(a).toBeInstanceOf(Number);
expect(() => JSON.parse('')).toThrow();
"deep equality": vergleicht Inhalte con Objekten / Arrays
assert.deepEqual([1, 2], [1, 2]);
expect([1, 2]).to.eql([1, 2]);
expect([1, 2]).toEqual([1, 2]);
"strict equality": verhält sich wie ===
- kann zum Vergleichen von Primitiven verwendet werden (oder zum Identitätsvergleich bei Objekten)
assert.equal('abc', 'abc');
expect('abc').to.equal('abc');
expect('abc').toBe('abc');
Tests werden meist mittels eines npm Scripts ausgeführt - z.B. via npm run test
(oder abgekürzt npm test
)
Jest: sucht standardmäßig nach Dateien in __tests__
-Ordnern und nach Dateien, die mit .test.js
oder .spec.js
enden
Mocha: sucht standardmäßig nach Dateien in dem Ordner test
(eigenes Muster z.B. via: mocha "src/**/*.{test,spec}.{js,jsx}"
)
Die Definition eines Tests beinhaltet üblicherweise:
Tests werden üblicherweise durch einen Aufruf von test()
oder it()
definiert
Beispiel mit den eingebauten Tools von node:
test(
'shortens "loremipsum" to "lor..." with limit 6',
() => {
const shortened = shorten('loremipsum', 6);
assert.equal(shortened, "lor...");
}
)
Tests können in Gruppen (und Untergruppen, ...) organisiert werden
Guppierung von Tests mittels node:test:
test('strings that are short enough', (t) => {
// t: test context
t.test('leaves "abc" unchanged with limit 3', () => {
assert.equal(shorten('abc', 3), 'abc');
});
t.test('leaves "a" unchanged with limit 1', () => {
assert.equal(shorten('a', 1), 'a');
});
});
Gruppieren von Tests mit Jest:
describe('strings that are short enough', () => {
test('leaves "abc" unchanged with limit 3', () => {
expect(shorten('abc', 3)).toEqual('abc');
});
test('leaves "a" unchanged with limit 1', () => {
expect(shorten('a', 3)).toEqual('a');
});
});
Manche Testlibraries können berichten, wie viel des Codes von Tests abgedeckt ist:
npx jest --coverage
in einem create-react-app Projekt:
npm test -- --coverage
Für Code, der vor bzw. nach jedem Test in einer Gruppe ausgeführt werden soll:
describe('database', () => {
beforeEach(() => {
createTestDB();
});
afterEach(() => {
clearTestDB();
});
it(/*...*/);
it(/*...*/);
});
schreibe Tests, die das Verhalten der String-Methode .replace()
testen
End-to-end Tests: ein Browser wird gesteuert, um mit einer Website zu interagieren und ihr Verhalten zu verifizieren
Tools:
verwendet im Hintergrund mocha und chai
zum Initialisieren, führe npx cypress open
aus
(erstelle Ordner cypress und Datei cypress.config.js im aktuellen Verzeichnis)
Öffnen der grafischen Oberfläche:
npx cypress open
Ausführen aller Tests im Terminal:
npx cypress run
Beispiel: Testen von Wikipedia:
in cypress/e2e/wikipedia.cy.js:
describe('wikipedia', () => {
beforeEach(() => {
cy.visit('https://en.wikipedia.org');
});
it("page title includes 'wikipedia'", () => {
cy.title().should('match', /wikipedia/i);
});
});
globales Abfragen von Elementen:
cy.contains("h1", "hello world")
cy.contains("hello world")
cy.get("main #name-input")
cy.get("main #name-input").first()
Abfragen von Unterelementen:
.find()
: ähnlich zu .get()
, aber für Unterelemente.contains()
cy.contains('li', 'TODO: write tests').find('button');
cy.contains('li', 'TODO: write tests').contains(
'button',
'delete'
);
Abfragen des Elternelements:
cy.contains('h1', 'section title').parent();
Interagieren mit Elementen:
cy.get("#reset-btn").click()
cy.get("#name-input").type("foo")
cy.get("#name-input").type("{esc}")
Beispiel für eine Assertion:
cy.get('#name-input').should('have.class', 'invalid');
andere Assertions:
cy.url().should("match", /\/items\/d+$/)
.should("have.class", "invalid")
.should("have.value", "")
.should("be.visible")
Beispiel: Suche auf Wikipedia
it('search', () => {
cy.get('#searchInput').type('cypress');
cy.get('#searchButton').click();
cy.get('p').first().should('include.text', 'Cypress');
});
Schreibe Tests für die Todo-Anwendung auf:
npm-Pakete:
wir werden jest als Test-Runner verwenden
Testen von Wikipedia:
import puppeteer from 'puppeteer';
test('wikipedia title', async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://en.wikipedia.org');
const pageTitle = await page.title();
expect(pageTitle).toMatch(/Wikipedia/);
await browser.close();
});
Restrukturierung für mehrere Tests:
let browser: puppeteer.Browser;
let page: puppeteer.Page;
beforeAll(async () => {
browser = await puppeteer.launch();
});
beforeEach(async () => {
page = await browser.newPage();
await page.goto('https://en.wikipedia.org');
});
afterAll(async () => {
await browser.close();
});
test('wikipedia title', async () => {
const pageTitle = await page.title();
expect(pageTitle).toMatch(/Wikipedia/);
});
Test, der tatsächlich ein Browser-Fenster öffnet:
beforeAll(async () => {
browser = await puppeteer.launch({ headless: false });
});
Abfragen von Elementen ist nicht trivial, da wir mit zwei separate JavaScript-Umgebungen arbeiten (node und Chromium)
Seiteninhalte abfragen:
page.$eval()
für Inhalte eines einzelnen Elementspage.$$eval()
zum Abfragen aller zutreffenden ElementeElemente abfragen, um mit ihnen zu interagieren:
page.$()
für ein einzelnes Elementpage.$$()
für ein Array aller zutreffenden EelmenteElemente abfragen, um auf deren Inhalte zuzugreifen:
const firstLinkText = await page.$eval(
'a',
(element) => element.innerHTML
);
const thirdLinkText = await page.$$eval(
'a',
(elements) => elements[2].innerHTML
);
Elemente abfragen, um mit ihnen zu interagieren:
const firstLink = await page.$('a');
await firstLink.click();
await page.waitForNavigation();
Beispiel: Suche auf Wikipedia
test('wikipedia search', async () => {
await page.click('#searchInput');
await page.keyboard.type('puppeteer');
await page.click('#searchButton');
await page.waitForNavigation();
const paragraphText = await page.$eval(
'p',
(element) => element.textContent
);
console.log(paragraphText);
expect(paragraphText).toMatch(/puppeteer/i);
});
Bemerkungen: page.keyboard.press("Enter") würde eine Volltextsuche auslösen; auf manchen Wikipedia-Seiten ist der erste Paragraph leer.
wichtige Methoden:
page.$()
page.$$()
page.$eval()
page.$$eval()
page.keyboard.type("abc")
page.keyboard.press("Enter")
page.click("#selector")
element.click()
page.waitForNavigation()
schreibe Tests für die Todo-App unter:
beinhaltet expect aus Jest
npm install @playwright/test
Installiere Browser (Chromium, Firefox, Webkit):
npx playwright install
Ausführen von Tests:
npx playwright test
Ausführen im "headed"-Modus (öffnet Browser-Fenster):
npx playwright test --headed
Testen von Wikipedia:
import { test, expect } from '@playwright/test';
test('wikipedia title', async ({ page }) => {
await page.goto('https://en.wikipedia.org');
const pageTitle = await page.title();
expect(pageTitle).toMatch(/Wikipedia/);
});
Umstrukturieren des Codes für mehrere Tests:
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('https://en.wikipedia.org');
});
test('wikipedia title', async ({ page }) => {
const pageTitle = await page.title();
expect(pageTitle).toMatch(/Wikipedia/);
});
Klicken auf einen Link:
await page.click('text=About Wikipedia');
Beispiel: Suchen auf Wikipedia
test('wikipedia search', async ({ page }) => {
await page.fill(
'input[aria-label="Search Wikipedia"]',
'foo'
);
await page.click('#searchButton');
await page.waitForNavigation();
const mainHeading = page.locator('h1');
await expect(mainHeading).toHaveText(/foo/i);
});
Bemerkungen: page.keyboard.press("Enter") würde eine Volltextsuche auslösen
Mocking: Simulieren von Objekten / Interfaces in einer Test-Umgebung
Beispiel: Mocken einer Netzwerkanfrage
import fetchMock from 'fetch-mock';
fetchMock.mock('https://example.com', { foo: 'bar' });
Mocken eines Moduls mittels jest.mock
:
jest.mock('axios', () => ({
get: () => Promise.resolve({ data: { foo: 'bar' } }),
}));
Mocking von Modulen via __mocks__ Ordnern:
__mocks__/fs.js
__mocks__/axios.js
src/foo.js
src/foo.test.js
src/__mocks__/foo.js
// src/foo.test.js
jest.mock('fs');
jest.mock('axios'); // optional for contents of node_modules
jest.mock('./foo');
Bemerkung: in einem create-react-app-Projekt wäre dies z.B. src/__mocks__/axios.js
anstatt __mocks__/axios.js
(siehe issue)
Mocken einer Funktion, die aufgerufen und später inspiziert werden kann:
const mockFn = jest.fn();
mockFn('foo');
expect(mockFn).toHaveBeenCalledWith('foo');