├── .github └── FUNDING.yml ├── License.md └── Readme.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: sapegin 2 | ko_fi: sapegin 3 | custom: https://www.buymeacoffee.com/sapegin 4 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 | 12 | 13 |

Vitest cheat sheet

14 | 15 |
16 | 17 | _This is a fork of the popular [Jest cheat sheet](https://github.com/sapegin/jest-cheat-sheet)._ 18 | 19 | [![Washing your code. A book on clean code for frontend developers](https://sapegin.me/images/washing-code-github.jpg)](https://sapegin.me/book/) 20 | 21 | ## Table of contents 22 | 23 | 24 | 25 | - [Getting started](#getting-started) 26 | - [Test structure](#test-structure) 27 | - [Basic test structure](#basic-test-structure) 28 | - [Test with grouping, setup and teardown code](#test-with-grouping-setup-and-teardown-code) 29 | - [Matchers](#matchers) 30 | - [Basic matchers](#basic-matchers) 31 | - [Truthiness](#truthiness) 32 | - [Numbers](#numbers) 33 | - [Strings](#strings) 34 | - [Arrays](#arrays) 35 | - [Objects](#objects) 36 | - [Exceptions](#exceptions) 37 | - [Snapshots](#snapshots) 38 | - [Misc](#misc) 39 | - [Promise matchers](#promise-matchers) 40 | - [Async tests](#async-tests) 41 | - [async/await](#asyncawait) 42 | - [Promises](#promises) 43 | - [Mocks](#mocks) 44 | - [Mock functions](#mock-functions) 45 | - [Returning, resolving and rejecting values](#returning-resolving-and-rejecting-values) 46 | - [Mock modules using `vi.mock()` method](#mock-modules-using-vimock-method) 47 | - [Mock modules using a mock file](#mock-modules-using-a-mock-file) 48 | - [Mock object methods](#mock-object-methods) 49 | - [Mock getters and setters](#mock-getters-and-setters) 50 | - [Clearing and restoring mocks](#clearing-and-restoring-mocks) 51 | - [Accessing the original module when using mocks](#accessing-the-original-module-when-using-mocks) 52 | - [Timer mocks](#timer-mocks) 53 | - [Data-driven tests](#data-driven-tests) 54 | - [Skipping tests](#skipping-tests) 55 | - [Testing modules with side effects](#testing-modules-with-side-effects) 56 | - [Resources](#resources) 57 | - [Contributing](#contributing) 58 | - [Sponsoring](#sponsoring) 59 | - [Author and license](#author-and-license) 60 | 61 | 62 | 63 | ## Getting started 64 | 65 | [See the official docs](https://vitest.dev/guide/) 66 | 67 | ## Test structure 68 | 69 | ### Basic test structure 70 | 71 | > [!NOTE] 72 | > In comparison to Jest, in Vitest you need to explicitly import all methods 73 | 74 | ```js 75 | import { expect, test } from 'vitest' 76 | import { makePoniesPink } from './makePoniesPink' 77 | 78 | test('make each pony pink', () => { 79 | const actual = makePoniesPink(['Alice', 'Bob', 'Eve']) 80 | expect(actual).toEqual(['Pink Alice', 'Pink Bob', 'Pink Eve']) 81 | }) 82 | ``` 83 | 84 | ### Test with grouping, setup and teardown code 85 | 86 | ```js 87 | import { expect, test, beforeAll, afterAll , beforeEach,afterEach} from 'vitest' 88 | import { makePoniesPink } from './makePoniesPink' 89 | 90 | describe('makePoniesPink', () => { 91 | beforeAll(() => { 92 | // Runs before all tests 93 | }) 94 | afterAll(() => { 95 | // Runs after all tests 96 | }) 97 | beforeEach(() => { 98 | // Runs before each test 99 | }) 100 | afterEach(() => { 101 | // Runs after each test 102 | }) 103 | 104 | test('make each pony pink', () => { 105 | const actual = makePoniesPink(['Alice', 'Bob', 'Eve']) 106 | expect(actual).toEqual(['Pink Alice', 'Pink Bob', 'Pink Eve']) 107 | }) 108 | ``` 109 | 110 | ## Matchers 111 | 112 | [Matchers docs](https://vitest.dev/api/expect.html) 113 | 114 | ### Basic matchers 115 | 116 | ```js 117 | expect(42).toBe(42) // Strict equality (===) 118 | expect(42).not.toBe(3) // Strict equality (!==) 119 | expect([1, 2]).toEqual([1, 2]) // Deep equality 120 | expect({ a: undefined, b: 2 }).toEqual({ b: 2 }) // Deep equality 121 | expect({ a: undefined, b: 2 }).not.toStrictEqual({ b: 2 }) // Strict equality (Jest 23+) 122 | ``` 123 | 124 | ### Truthiness 125 | 126 | ```js 127 | // Matches anything that an if statement treats as true (true, 1, 'hello', {}, [], 5.3) 128 | expect('foo').toBeTruthy() 129 | // Matches anything that an if statement treats as false (false, 0, '', null, undefined, NaN) 130 | expect('').toBeFalsy() 131 | // Matches only null 132 | expect(null).toBeNull() 133 | // Matches only undefined 134 | expect(undefined).toBeUndefined() 135 | // The opposite of toBeUndefined 136 | expect(7).toBeDefined() 137 | // Matches true or false 138 | expect(true).toEqual(expect.any(Boolean)) 139 | ``` 140 | 141 | ### Numbers 142 | 143 | ```js 144 | expect(2).toBeGreaterThan(1) 145 | expect(1).toBeGreaterThanOrEqual(1) 146 | expect(1).toBeLessThan(2) 147 | expect(1).toBeLessThanOrEqual(1) 148 | expect(0.2 + 0.1).toBeCloseTo(0.3, 5) 149 | expect(NaN).toEqual(expect.any(Number)) 150 | ``` 151 | 152 | ### Strings 153 | 154 | ```js 155 | expect('long string').toMatch('str') 156 | expect('string').toEqual(expect.any(String)) 157 | expect('coffee').toMatch(/ff/) 158 | expect('pizza').not.toMatch('coffee') 159 | expect(['pizza', 'coffee']).toEqual([expect.stringContaining('zz'), expect.stringMatching(/ff/)]) 160 | ``` 161 | 162 | ### Arrays 163 | 164 | ```js 165 | expect([]).toEqual(expect.any(Array)) 166 | expect(['Alice', 'Bob', 'Eve']).toHaveLength(3) 167 | expect(['Alice', 'Bob', 'Eve']).toContain('Alice') 168 | expect([{ a: 1 }, { a: 2 }]).toContainEqual({ a: 1 }) 169 | expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(['Alice', 'Bob'])) 170 | ``` 171 | 172 | ### Objects 173 | 174 | ```js 175 | expect({ a: 1 }).toHaveProperty('a') 176 | expect({ a: 1 }).toHaveProperty('a', 1) 177 | expect({ a: { b: 1 } }).toHaveProperty('a.b') 178 | expect({ a: 1, b: 2 }).toMatchObject({ a: 1 }) 179 | expect({ a: 1, b: 2 }).toMatchObject({ 180 | a: expect.any(Number), 181 | b: expect.any(Number), 182 | }) 183 | expect([{ a: 1 }, { b: 2 }]).toEqual([ 184 | expect.objectContaining({ a: expect.any(Number) }), 185 | expect.anything(), 186 | ]) 187 | ``` 188 | 189 | ### Exceptions 190 | 191 | ```js 192 | // const fn = () => { throw new Error('Out of cheese!') } 193 | expect(fn).toThrowError() 194 | expect(fn).toThrowError('Out of cheese') 195 | expect(fn).toThrowErrorMatchingSnapshot() 196 | expect(fn).toThrowErrorMatchingInlineSnapshot() 197 | 198 | // const fn = () => { console.error('Out of cheese!') } 199 | expect(fn).not.toThrowError() 200 | expect(fn).not.toThrowError('Out of cheese') 201 | ``` 202 | 203 | ### Snapshots 204 | 205 | ```js 206 | expect(node).toMatchSnapshot() 207 | expect(user).toMatchSnapshot({ 208 | date: expect.any(Date), 209 | }) 210 | expect(user).toMatchInlineSnapshot() 211 | ``` 212 | 213 | ### Misc 214 | 215 | ```js 216 | expect(new A()).toBeInstanceOf(A) 217 | expect(() => {}).toEqual(expect.any(Function)) 218 | expect('pizza').toEqual(expect.anything()) 219 | ``` 220 | 221 | ### Promise matchers 222 | 223 | > [!WARNING] 224 | > Don’t forget to return the result of the `expect()` method from each test case. 225 | 226 | ```js 227 | test('resolves with a lemon', () => { 228 | return expect(Promise.resolve('lemon')).resolves.toBe('lemon') 229 | }) 230 | test('rejects with an octopus', () => { 231 | return expect(Promise.reject('octopus')).rejects.toBeDefined() 232 | }) 233 | test('rejects with an error', () => { 234 | return expect(Promise.reject(Error('pizza'))).rejects.toThrow() 235 | }) 236 | ``` 237 | 238 | Or with async/await: 239 | 240 | > [!WARNING] 241 | > Don’t forget to `await` the `expect()` method in each test case. 242 | 243 | ```js 244 | test('resolves with a lemon', async () => { 245 | await expect(Promise.resolve('lemon')).resolves.toBe('lemon') 246 | }) 247 | test('doesn’t resolve with an octopus', async () => { 248 | await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus') 249 | }) 250 | ``` 251 | 252 | ## Async tests 253 | 254 | ### async/await 255 | 256 | ```js 257 | test('async test', async () => { 258 | const result = await runAsyncOperation() 259 | expect(result).toBe(true) 260 | }) 261 | ``` 262 | 263 | ### Promises 264 | 265 | _Return_ a Promise from your test: 266 | 267 | ```js 268 | test('async test', () => { 269 | return runAsyncOperation().then((result) => { 270 | expect(result).toBe(true) 271 | }) 272 | }) 273 | ``` 274 | 275 | ## Mocks 276 | 277 | ### Mock functions 278 | 279 | [Mock functions docs](https://jestjs.io/docs/en/mock-function-api) 280 | 281 | ```js 282 | import { expect, vi } from 'vitest' 283 | 284 | // const fn = vi.fn() 285 | // const fn = vi.fn().mockName('Unicorn') -- named mock, Jest 22+ 286 | expect(fn).toHaveBeenCalled() // Function was called 287 | expect(fn).not.toHaveBeenCalled() // Function was *not* called 288 | expect(fn).toHaveBeenCalledTimes(1) // Function was called only once 289 | expect(fn).toHaveBeenCalledWith(arg1, expect.anything(), arg3) // Any of calls was with these arguments 290 | expect(fn).toHaveBeenLastCalledWith(arg1, arg2) // Last call was with these arguments 291 | expect(fn).toHaveBeenNthCalledWith(callNumber, args) // Nth call was with these arguments (Jest 23+) 292 | expect(fn).toHaveReturnedTimes(2) // Function was returned without throwing an error (Jest 23+) 293 | expect(fn).toHaveReturnedWith(value) // Function returned a value (Jest 23+) 294 | expect(fn).toHaveLastReturnedWith(value) // Last function call returned a value (Jest 23+) 295 | expect(fn).toHaveNthReturnedWith(value) // Nth function call returned a value (Jest 23+) 296 | 297 | // Multiple calls 298 | expect(fn.mock.calls).toEqual([ 299 | ['first', 'call', 'args'], 300 | ['second', 'call', 'args'], 301 | ]) 302 | 303 | // fn.mock.calls[0][0] — the first argument of the first call 304 | expect(fn.mock.calls[0][0]).toBe(2) 305 | ``` 306 | 307 | You can also use snapshots: 308 | 309 | ```js 310 | test('call the callback', () => { 311 | const callback = vi.fn().mockName('Unicorn') // mockName is available in Jest 22+ 312 | fn(callback) 313 | expect(callback).toMatchSnapshot() 314 | // -> 315 | // [MockFunction Unicorn] { 316 | // "calls": Array [ 317 | // ... 318 | }) 319 | ``` 320 | 321 | And pass an implementation to `vi.fn` function: 322 | 323 | ```js 324 | const callback = vi.fn(() => true) 325 | ``` 326 | 327 | ### Returning, resolving and rejecting values 328 | 329 | Your mocks can return values: 330 | 331 | ```js 332 | const callback = vi.fn().mockReturnValue(true) 333 | const callbackOnce = vi.fn().mockReturnValueOnce(true) 334 | ``` 335 | 336 | Or resolve values: 337 | 338 | ```js 339 | const promise = vi.fn().mockResolvedValue(true) 340 | const promiseOnce = vi.fn().mockResolvedValueOnce(true) 341 | ``` 342 | 343 | They can even reject values: 344 | 345 | ```js 346 | const failedPromise = vi.fn().mockRejectedValue('Error') 347 | const failedPromiseOnce = vi.fn().mockRejectedValueOnce('Error') 348 | ``` 349 | 350 | You can even combine these: 351 | 352 | ```js 353 | const callback = vi.fn().mockReturnValueOnce(false).mockReturnValue(true) 354 | 355 | // -> 356 | // call 1: false 357 | // call 2+: true 358 | ``` 359 | 360 | ### Mock modules using `vi.mock()` method 361 | 362 | [Mock modules](https://vitest.dev/api/mock.html) 363 | 364 | ```js 365 | import { vi } from 'vitest' 366 | 367 | // The original lodash/memoize should exist 368 | vi.mock('lodash/memoize', () => (a) => a) 369 | // The original lodash/memoize isn’t required 370 | vi.mock('lodash/memoize', () => (a) => a, { virtual: true }) 371 | ``` 372 | 373 | ### Mock modules using a mock file 374 | 375 | 1. Create a file like `__mocks__/lodash/memoize.js`: 376 | 377 | ```js 378 | module.exports = (a) => a 379 | ``` 380 | 381 | 2. Add to your test: 382 | 383 | ```js 384 | vi.mock('lodash/memoize') 385 | ``` 386 | 387 | ### Mock object methods 388 | 389 | ```js 390 | import { vi } from 'vitest' 391 | 392 | const spy = vi.spyOn(console, 'log').mockImplementation(() => {}) 393 | expect(console.log.mock.calls).toEqual([['dope'], ['nope']]) 394 | spy.mockRestore() 395 | ``` 396 | 397 | ```js 398 | import { vi } from 'vitest' 399 | 400 | const spy = vi.spyOn(ajax, 'request').mockImplementation(() => Promise.resolve({ success: true })) 401 | expect(spy).toHaveBeenCalled() 402 | spy.mockRestore() 403 | ``` 404 | 405 | ### Mock getters and setters 406 | 407 | ```js 408 | import { vi } from 'vitest' 409 | 410 | const location = {} 411 | const getTitle = vi.spyOn(location, 'title', 'get').mockImplementation(() => 'pizza') 412 | const setTitle = vi.spyOn(location, 'title', 'set').mockImplementation(() => {}) 413 | ``` 414 | 415 | ### Clearing and restoring mocks 416 | 417 | For one mock: 418 | 419 | ```js 420 | fn.mockClear() // Clears mock usage date (fn.mock.calls, fn.mock.instances) 421 | fn.mockReset() // Clears and removes any mocked return values or implementations 422 | fn.mockRestore() // Resets and restores the initial implementation 423 | ``` 424 | 425 | > [!NOTE] 426 | > The `mockRestore()` method works only with mocks created by `vi.spyOn()`. 427 | 428 | For all mocks: 429 | 430 | ```js 431 | vi.clearAllMocks() 432 | vi.resetAllMocks() 433 | vi.restoreAllMocks() 434 | ``` 435 | 436 | ### Accessing the original module when using mocks 437 | 438 | ```js 439 | import { vi } from 'vitest' 440 | 441 | vi.mock('./example.js', async () => { 442 | const axios = await vi.importActual('./example.js') 443 | 444 | return { ...axios, get: vi.fn() } 445 | }) 446 | ``` 447 | 448 | ### Timer mocks 449 | 450 | [Fake times](https://vitest.dev/api/vi.html#fake-timers) 451 | 452 | Write synchronous test for code that uses native timer functions (`setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`). 453 | 454 | ```js 455 | // Enable fake timers 456 | vi.useFakeTimers() 457 | 458 | test('kill the time', () => { 459 | const callback = vi.fn() 460 | 461 | // Run some code that uses setTimeout or setInterval 462 | const actual = someFunctionThatUseTimers(callback) 463 | 464 | // Fast-forward until all timers have been executed 465 | vi.runAllTimers() 466 | 467 | // Check the results synchronously 468 | expect(callback).toHaveBeenCalledTimes(1) 469 | }) 470 | ``` 471 | 472 | Or adjust timers by time with [vi.advanceTimersByTime()](https://vitest.dev/api/vi.html#vi-advancetimersbytime): 473 | 474 | ```js 475 | // Enable fake timers 476 | vi.useFakeTimers() 477 | 478 | test('kill the time', () => { 479 | const callback = vi.fn() 480 | 481 | // Run some code that uses setTimeout or setInterval 482 | const actual = someFunctionThatUseTimers(callback) 483 | 484 | // Fast-forward for 250 ms 485 | vi.advanceTimersByTime(250) 486 | 487 | // Check the results synchronously 488 | expect(callback).toHaveBeenCalledTimes(1) 489 | }) 490 | ``` 491 | 492 | Use [vi.runOnlyPendingTimers()](https://vitest.dev/api/vi.html#vi-runonlypendingtimers) for special cases. 493 | 494 | > ![NOTE] 495 | > You should call `vi.useFakeTimers()` in your test case to use other fake timer methods. 496 | 497 | ## Data-driven tests 498 | 499 | Run the same test with different data: 500 | 501 | ```js 502 | test.each([ 503 | [1, 1, 2], 504 | [1, 2, 3], 505 | [2, 1, 3], 506 | ])('.add(%s, %s)', (a, b, expected) => { 507 | expect(a + b).toBe(expected) 508 | }) 509 | ``` 510 | 511 | Or the same using template literals: 512 | 513 | ```js 514 | test.each` 515 | a | b | expected 516 | ${1} | ${1} | ${2} 517 | ${1} | ${2} | ${3} 518 | ${2} | ${1} | ${3} 519 | `('returns $expected when $a is added $b', ({ a, b, expected }) => { 520 | expect(a + b).toBe(expected) 521 | }) 522 | ``` 523 | 524 | Or on `describe` level: 525 | 526 | ```js 527 | describe.each([['mobile'], ['tablet'], ['desktop']])('checkout flow on %s', (viewport) => { 528 | test('displays success page', () => { 529 | // 530 | }) 531 | }) 532 | ``` 533 | 534 | [describe.each() docs](https://vitest.dev/api/#describe-each), [test.each() docs](https://vitest.dev/api/#test-each), 535 | 536 | ## Skipping tests 537 | 538 | Don’t run these tests: 539 | 540 | ```js 541 | describe.skip('makePoniesPink'... 542 | tests.skip('make each pony pink'... 543 | ``` 544 | 545 | Run only these tests: 546 | 547 | ```js 548 | describe.only('makePoniesPink'... 549 | tests.only('make each pony pink'... 550 | ``` 551 | 552 | ## Testing modules with side effects 553 | 554 | Node.js and Vitest cache modules you `import`. To test modules with side effects you’ll need to reset the module registry between tests: 555 | 556 | ```js 557 | const modulePath = '../module-to-test' 558 | 559 | afterEach(() => { 560 | vi.resetModules() 561 | }) 562 | 563 | test('first test', async () => { 564 | // Prepare conditions for the first test 565 | const result = await import(modulePath) 566 | expect(result).toMatchSnapshot() 567 | }) 568 | 569 | test('second text', async () => { 570 | // Prepare conditions for the second test 571 | const fn = () => await import(modulePath) 572 | expect(fn).toThrowError() 573 | }) 574 | ``` 575 | 576 | ## Resources 577 | 578 | - [Vitest site](https://vitest.dev/) 579 | - [Modern React testing, part 1: best practices](https://blog.sapegin.me/all/react-testing-1-best-practices/) by Artem Sapegin 580 | - [Modern React testing, part 2: Jest and Enzyme](https://blog.sapegin.me/all/react-testing-2-jest-and-enzyme/) by Artem Sapegin 581 | - [Modern React testing, part 3: Jest and React Testing Library](https://blog.sapegin.me/all/react-testing-3-jest-and-react-testing-library/) by Artem Sapegin 582 | - [Modern React testing, part 4: Cypress and Cypress Testing Library](https://sapegin.me/blog/react-testing-4-cypress/) by Artem Sapegin 583 | - [Modern React testing, part 5: Playwright](https://sapegin.me/blog/react-testing-5-playwright/) by Artem Sapegin 584 | 585 | --- 586 | 587 | ## Contributing 588 | 589 | Improvements are welcome! Open an issue, or send a pull request. 590 | 591 | ## Sponsoring 592 | 593 | This software has been developed with lots of coffee, buy me one more cup to keep it going. 594 | 595 | Buy Me A Coffee 596 | 597 | ## Author and license 598 | 599 | [Artem Sapegin](https://sapegin.me/), a frontend engineer at [Stage+](https://www.stage-plus.com) and the creator of [React Styleguidist](https://react-styleguidist.js.org/). I also write about frontend at [my blog](https://sapegin.me/blog/). 600 | 601 | CC0 1.0 Universal license, see the included [License.md](/License.md) file. 602 | --------------------------------------------------------------------------------