diff --git a/slides.md b/slides.md index b73a20d..7445180 100644 --- a/slides.md +++ b/slides.md @@ -47,7 +47,7 @@ Test runners can be part of a larger **test framework** or standalone tools. - Test runners are tools designed to execute your test suites and report the results. They are essential in automating the testing process. - Python: `pytest` is widely appreciated for its powerful features and simple syntax, making it suitable for both simple and complex projects. - Java: `JUnit` is the de facto standard for unit testing in Java development, known for its rich annotation-based configuration. -- JavaScript: `Mocha` is a flexible test framework with a focus on asynchronous testing, offering rich features for running tests in Node.js and the browser. +- JavaScript: `Jest` is a flexible test framework with a focus on asynchronous testing, offering rich features for running tests in Node.js and the browser. - .NET: `NUnit` is a popular choice for .NET developers, similar to JUnit but with a focus on the .NET framework. @@ -62,7 +62,7 @@ Choosing the right test runner involves considering the programming language, pr - Test Runner: A tool that executes tests and reports the results. It is responsible for loading your test code, running it, and then providing feedback. - Testing Framework: Provides the structure and guidelines for writing tests. It includes assertions, test cases, and test suites, but doesn't run tests by itself. - The main difference lies in their roles; while a testing framework defines how to write tests, a test runner actually executes them. -- Some tools, like `pytest` and `Mocha`, combine both functionalities, acting as both test runners and frameworks. +- Some tools, like `pytest` and `Jest`, combine both functionalities, acting as both test runners and frameworks. --- @@ -89,7 +89,7 @@ Choosing the right test runner involves considering the programming language, pr # Workshop setup -- This workshop will introduce to the Node.js test runner with 11 excercises +- This workshop will introduce to the Node.js test runner with a series of exercises - At each step you're asked to use a different test runner feature - The 💡 icon indicates hints @@ -539,17 +539,124 @@ You can reference the [`--import` official documentation](https://nodejs.org/api --- -# Other useful resources +# A11 Timers + +
+ +- Timers are crucial for testing **time-dependent functionality** in applications, such as debouncing, throttling, or any operation that relies on time delays. +- Using real timers in tests can lead to unpredictable results and slow down the testing process, as tests have to wait for the actual time to pass. +- The Node.js test runner offers a way to **mock timers**, enabling tests to simulate the passage of time instantly. +- Developers can enable mocked versions of timers like `setTimeout` and `setInterval` that can be controlled programmatically. + +
--- -# Thanks For Having Us! +# A11 The problem -## 👏👏👏 +- In the `test` folder, there is a `index.test.js` file +- The function to test, contains a `setTimeout` +- During testing, this can lead to slow and unpredictable tests +- Apply [timers mocking](https://nodejs.org/api/test.html#timers) in the test file -```` +--- + +# A11 Solution 💡 + +```javascript +test('delayedHello executes the callback after the specified delay', () => { + const fn = mock.fn() + mock.timers.enable({ apis: ['setTimeout'] }) + delayedHello(fn, 5000) + + // Initially, the callback has not been called + assert.strictEqual(fn.mock.calls.length, 0) + // Advance time by 5000 milliseconds + mock.timers.tick(5000) + // Now, the callback should have been called once + assert.strictEqual(fn.mock.calls.length, 1) + assert.strictEqual(fn.mock.calls[0][0], 'Hello, World!') + + mock.timers.reset() +}) ``` +--- + +# A12 Context + +
+ +- The `context` object is essential for managing test lifecycles, including setup and teardown processes. +- It provides hooks (`before`, `beforeEach`, `after`, `afterEach`) for preparing and cleaning up before and after tests or a group of tests. +- Enables control over test execution through methods like `skip` (to bypass tests), `todo` (to mark tests as pending), and `runOnly` (to execute only specified tests). +- Offers a `diagnostic` method for logging debug information and a signal property for aborting tests programmatically. +- Supports **hierarchical test structuring** with the test method, allowing for the creation of subtests that inherit the context of their parent test. +- Facilitates grouping related tests by using `beforeEach` and `afterEach` hooks for shared setup and cleanup, ensuring a well-organized and maintainable test suite. + +
+ +--- + +# A12 The problem + +- In this exercise about context, we will focus on child tests (also known as subtests) +- In the file `index.test.js` you will find multiple tests for the `sum` and the `average` functions +- Group together all the subtests related to the same function + +--- + +# A12 Solution 💡 + +```javascript +// Grouping tests for `sum` function +test('sum function tests', async t => { + await t.test('Sum works correctly with valid input', () => { + assert.deepStrictEqual(sum([1, 2, 3]), 6) + }) + + await t.test('Sum returns 0 in case of empty array', () => { + assert.deepStrictEqual(sum([]), 0) + }) + + await t.test('Sum throws in case of bad input', () => { + assert.throws(() => sum('abc'), { + message: 'Input must be an array of numbers' + }) + }) +}) ``` -```` + +--- + +# A12 Solution 💡 (2) + +```javascript +// Grouping tests for `average` function +test('average function tests', async t => { + await t.test('Average works correctly with valid input', () => { + assert.deepStrictEqual(average([1, 2, 3]), 2) + }) + + await t.test('Average returns 0 in case of empty array', () => { + assert.deepStrictEqual(average([]), 0) + }) + + await t.test('Average throws in case of bad input', () => { + assert.throws(() => average('abc'), { + message: 'Input must be an array of numbers' + }) + }) +}) +``` + +--- + +# Other useful resources + +--- + +# Thanks For Having Us! + +## 👏👏👏 diff --git a/src/a11-timers/README.md b/src/a11-timers/README.md new file mode 100644 index 0000000..f6ef1ba --- /dev/null +++ b/src/a11-timers/README.md @@ -0,0 +1,7 @@ +# a11-timers + +- Open the file `test/index.test.js`. + +- Use the [Timers API](https://nodejs.org/api/test.html#timers) to mock the `setTimeout` + +- Run in the terminal `node --test`. diff --git a/src/a11-timers/package.json b/src/a11-timers/package.json new file mode 100644 index 0000000..0ef2230 --- /dev/null +++ b/src/a11-timers/package.json @@ -0,0 +1,14 @@ +{ + "name": "a11-timers", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "index.js", + "scripts": { + "test": "node --test", + "solution": "node --test ./test/solution.test.js" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/src/a11-timers/src/index.js b/src/a11-timers/src/index.js new file mode 100644 index 0000000..9799d9d --- /dev/null +++ b/src/a11-timers/src/index.js @@ -0,0 +1,5 @@ +export function delayedHello(callback, delay) { + setTimeout(() => { + callback('Hello, World!') + }, delay) +} diff --git a/src/a11-timers/test/index.test.js b/src/a11-timers/test/index.test.js new file mode 100644 index 0000000..6ce971c --- /dev/null +++ b/src/a11-timers/test/index.test.js @@ -0,0 +1,24 @@ +/* eslint-disable no-unused-vars */ +import assert from 'node:assert' +import { delayedHello } from '../src/index.js' +import { test, mock } from 'node:test' + +test('delayedHello executes the callback after the specified delay', () => { + const fn = mock.fn() + + // Enable mocking of setTimeout + + // Call the function with a mock callback and a delay + //delayedHello(fn, 5000) + + // Initially, the callback has not been called + //assert.strictEqual(fn.mock.calls.length, 0) + + // Advance time by 5000 milliseconds + + // Now, the callback should have been called once + //assert.strictEqual(fn.mock.calls.length, 1) + //assert.strictEqual(fn.mock.calls[0][0], 'Hello, World!') + + // Reset mock timers after the test +}) diff --git a/src/a11-timers/test/solution.test.js b/src/a11-timers/test/solution.test.js new file mode 100644 index 0000000..4e42773 --- /dev/null +++ b/src/a11-timers/test/solution.test.js @@ -0,0 +1,20 @@ +import assert from 'node:assert' +import { delayedHello } from '../src/index.js' +import { test, mock } from 'node:test' + +test('delayedHello executes the callback after the specified delay', () => { + const fn = mock.fn() + + mock.timers.enable({ apis: ['setTimeout'] }) + delayedHello(fn, 5000) + + // Initially, the callback has not been called + assert.strictEqual(fn.mock.calls.length, 0) + // Advance time by 5000 milliseconds + mock.timers.tick(5000) + // Now, the callback should have been called once + assert.strictEqual(fn.mock.calls.length, 1) + assert.strictEqual(fn.mock.calls[0].arguments[0], 'Hello, World!') + + mock.timers.reset() +}) diff --git a/src/a12-context/README.md b/src/a12-context/README.md new file mode 100644 index 0000000..5f92736 --- /dev/null +++ b/src/a12-context/README.md @@ -0,0 +1,7 @@ +# a12-context + +- Open the file `test/index.test.js`. + +- Group all the subtests of a specific function with a context + +- Run in the terminal `node --test`. diff --git a/src/a12-context/package.json b/src/a12-context/package.json new file mode 100644 index 0000000..ca14aa7 --- /dev/null +++ b/src/a12-context/package.json @@ -0,0 +1,14 @@ +{ + "name": "a12-context", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "index.js", + "scripts": { + "test": "node --test", + "solution": "node --test ./test/solution.test.js" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/src/a12-context/src/index.js b/src/a12-context/src/index.js new file mode 100644 index 0000000..ea97b09 --- /dev/null +++ b/src/a12-context/src/index.js @@ -0,0 +1,20 @@ +export function sum(numbers) { + if (!Array.isArray(numbers)) { + throw new Error('Input must be an array of numbers') + } + + return numbers.reduce((acc, num) => acc + num, 0) +} + +export function average(numbers) { + if (!Array.isArray(numbers)) { + throw new Error('Input must be an array of numbers') + } + + if (numbers.length === 0) { + return 0 + } + + const sum = numbers.reduce((acc, num) => acc + num, 0) + return sum / numbers.length +} diff --git a/src/a12-context/test/index.test.js b/src/a12-context/test/index.test.js new file mode 100644 index 0000000..e1744f1 --- /dev/null +++ b/src/a12-context/test/index.test.js @@ -0,0 +1,31 @@ +import { average, sum } from '../src/index.js' +import { test } from 'node:test' +import assert from 'node:assert' + +test('Sum works correctly with valid input', () => { + assert.deepStrictEqual(sum([1, 2, 3]), 6) +}) + +test('Sum returns 0 in case of empty array', () => { + assert.deepStrictEqual(sum([]), 0) +}) + +test('Sum throws in case of bad input', () => { + assert.throws(() => sum('abc'), { + message: 'Input must be an array of numbers' + }) +}) + +test('Average works correctly with valid input', () => { + assert.deepStrictEqual(average([1, 2, 3]), 2) +}) + +test('Average returns 0 in case of empty array', () => { + assert.deepStrictEqual(average([]), 0) +}) + +test('Average throws in case of bad input', () => { + assert.throws(() => average('abc'), { + message: 'Input must be an array of numbers' + }) +}) diff --git a/src/a12-context/test/solution.test.js b/src/a12-context/test/solution.test.js new file mode 100644 index 0000000..5c188cc --- /dev/null +++ b/src/a12-context/test/solution.test.js @@ -0,0 +1,37 @@ +import { average, sum } from '../src/index.js' +import { test } from 'node:test' +import assert from 'node:assert' + +// Grouping tests for `sum` function +test('sum function tests', async t => { + await t.test('Sum works correctly with valid input', () => { + assert.deepStrictEqual(sum([1, 2, 3]), 6) + }) + + await t.test('Sum returns 0 in case of empty array', () => { + assert.deepStrictEqual(sum([]), 0) + }) + + await t.test('Sum throws in case of bad input', () => { + assert.throws(() => sum('abc'), { + message: 'Input must be an array of numbers' + }) + }) +}) + +// Grouping tests for `average` function +test('average function tests', async t => { + await t.test('Average works correctly with valid input', () => { + assert.deepStrictEqual(average([1, 2, 3]), 2) + }) + + await t.test('Average returns 0 in case of empty array', () => { + assert.deepStrictEqual(average([]), 0) + }) + + await t.test('Average throws in case of bad input', () => { + assert.throws(() => average('abc'), { + message: 'Input must be an array of numbers' + }) + }) +})