JavaScript Testing Practices and Principles by Kent C. Dodds.md 4.66 KB
Newer Older
Derek Knox's avatar
Derek Knox committed
1
2
# JavaScript Testing Practices and Principles by Kent C. Dodds

3
4
[https://frontendmasters.com/courses/testing-practices-principles/](https://frontendmasters.com/courses/testing-practices-principles/)

Derek Knox's avatar
Derek Knox committed
5
6
## Unit Tests

7
Tests that assert granular functionality (function level) and allow mocking
Derek Knox's avatar
Derek Knox committed
8

9
10
11
12
13
- Test suite : `file.spec.js` :: Test : `it`/`test`
    - `it`/`test` anatomy:
        - arrange
        - act
        - assert
14
- Jest:
Derek Knox's avatar
Derek Knox committed
15
16
17
18
    - `toBe`: aka `===`
    - `toEqual`: aka `lodash.isEqual` aka "same shape and values"
    - `toMatchObject`: aka "partial shape with values match"
    - `toHaveBeenCalled...`: various mock function assertions for calls, call count, and return values
19
        - `toHaveBeenCalledTimes(x)` is a defensive programming win over `toHaveBeenCalled()` as its less error prone to test modifications
Derek Knox's avatar
Derek Knox committed
20
21
22
23
    - `expect.any`: useful for schema testing
    - watch mode
        - `o`: to only run tests related to changed files
        - `p`: to filter filename via regex
24
25
- When writing tests ask:
    > What is the use case we want to verify when testing this function?
Derek Knox's avatar
Derek Knox committed
26
- Keep logic to a minimum, don't reproduce your source, and favor literals (`...` spread and stay DRY AF)
27
- Steps:
Derek Knox's avatar
Derek Knox committed
28
    - `import` module/function that does the work you want to test
29
30
31
    - `buildXyz` spec helper preparation (DRY reuse)
        - `beforeEach`/`afterEach` shared create/destroy cache for *test suite*
    - `describe` description as method name
32
        - `beforeEach`/`afterEach` shared create/destroy cache for *test*
33
34
35
    - `it`/`test` individual testing of function use cases that can run in total isolation of other tests
        - arrange/act (input)
        - assert (output)
36
37
            - primary focus: happy path
            - secondary focus: exception cases (sad paths)
Derek Knox's avatar
Derek Knox committed
38
39
40

## Mocks

41
A fake version of something, often a function, function payload, or module
42

43
44
45
46
47
48
49
50
- Jest:
    - `spyOn`: allows mocking the specific function on a specific object without mutating it 
        - `jest.spyOn(someObj, 'someFnKey');`
        - `someObj.someFnKey.mockImplementation(() => {});`
        - `someObj.someFnKey.mockRestore()`
        - However, it's ideal to mock the module itself vs. one-off `spyOn` -> `mockImplementation` -> `mockRestore` sequences
    - `jest.fn()`: common mock function
        - `jest.fn().mockReturnValue`: shorthand for returning specific values from the mock function
51
- Mocking has its place, but be weary that mocking results is taking a step away from how your software actually functions
52
    - I think this is where my mental barrier and "redundancy" thoughts come from as I feel its a case of duplicating what has already been coded        
53

54
### [Mocks vs. Stubs via Martin Fowler](https://martinfowler.com/articles/mocksArentStubs.html#:~:text=Stubs%20provide%20canned%20answers%20to,programmed%20in%20for%20the%20test.&text=Mocks%20are%20what%20we%20are,they%20are%20expected%20to%20receive.)
55

56
*Test Doubles*: generic term for any kind of pretend object used in place of a real object for testing purposes
57
58
59
60
61
62
63
64
65
66

>>>
- **Dummy** objects are passed around but never actually used. Usually they are just used to fill parameter lists.
- **Fake** objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
- **Stubs** provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
- **Spies** are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
- **Mocks** are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
>>>


67
## Testing Practices
68

69
- When testing UI componenets (via [Vue Test Utils](https://vue-test-utils.vuejs.org/guides/#knowing-what-to-test)):
70
    > A single test case would assert that some input (user interaction or change of props) provided to the component results in the expected output (render result or emitted custom events)
71
72
73
- Test Object Factories can be a useful DRY strategy for use in `beforeEach` or `it`/`test` to get consistent reusable payloads for use in tests
- TDD works well when it's very clear what work does and does not need to be done
- Just like normal programming stay DRY
74
- Mocking Vue component services is common via `jest.mock('path/to/service', mockImplementation)`
75
76
77
78
79
80

## Integration

Tests that assert units work together without mocking (use the real services, APIs, etc.)

- Where unit tests assert the happy path and edge cases, integration tests typically just assert the happy path (due to complexity and maintainability)
81