DEV Community

Cover image for Study Notes for Kent C. Dodds' Javascript Testing Course
Annie Taylor Chen
Annie Taylor Chen

Posted on • Edited on

Study Notes for Kent C. Dodds' Javascript Testing Course

This is a mini guide for developers who are new to testing. The lessons are mainly learnt from Kent C. Dodds' Javascript Testing course. Kent C. Dodds is the library author for Testing Library, which is the official recommendation for Create React App.

General testing strategies for frontend apps

  1. Static test/format - Eslint and Prettier
    To eliminate type errors and make code looks easy to read and formatted consistently.

  2. Unit test - Jest + React testing library
    Test individual key-components

  3. Integration and Snapshot tests - Jest + MSW
    Render the login pages with different responses from the metadata endpoint and see that buttons and forms are created properly

  4. End to End (e2e) test - Cypress + BrowserStack
    Implement cypress tests that run our login flow. Run the tests with BrowserStack to get coverage in different browsers. Integrate to GitHub to require approval before release

  5. Acceptance tests/Visual Regression Test - Percy from BrowserStack (without AI) or Applitools (with AI)
    Get screenshots

  6. Synthetic tests and monitoring - Datadog
    Implement synthetic tests in Datadog that runs different flows. Add real user monitoring in Datadog

Notes from the course:

0. Tests types, configuration

  • What are unit, static, integration and e2e tests?
    The explanation and code samples here:
    https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests
    this post also talks about different levels of tests and the pitfalls. The more tests you have, the higher the trophy you get at, the slower the tests will run (because of too many tests) and more money it will cost. Manual testing can always be expensive. Use strategies that suit your business needs and budget.

  • How do I use Node debugger and Chrome dev tool while running my tests?
    Add a debugger in your code where you want to pause.
    Add a script like this

"test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --watch"

It means we are using node’s inspect break, it would mean node will stop process, and we pass the jest binary to node, since jest will run all the tests in parallel, we want to use “runInBand” to run it one by one.
Then go to “chrome://inspect” in your chrome browser, and you will see the the inspect appearing in “Remote Target” section. Click on the “Inspect”, you will have a browser pop up where you can check call stacks etc.

1. Static

  • How to configure Eslint and Prettier?
    In the eslintrc files, the rules can be found on https://eslint.org/docs/user-guide/configuring
    In the prettierrc, the rules for formatting can be found on https://prettier.io/playground/, click the “show options” in the bottom left, then copy the config JSON.
    It is also recommended to use the extensions within your VSCode IDE so you can see errors while you code.

  • What do those ignore files such as eslintignore do?
    So the linter won’t check errors for the files listed here. For instance, if you run build we will have a dist folder, and we don’t want the linter to check for errors here.
    You can configure it in package json like this:

”lint”: “eslint --ignore-path .gitignore . “

It means when you run npm run lint the linter will seek ignore path, which is specified in our gitignore file and don’t check those, otherwise check the rest in the repo.

  • What are rc files?
    In short:
    They're not specific to node.
    They're just another file
    As far as formats, they can be almost anything — it just depends on what you'll use to parse and read them. YAML, JSON, and ini are probably the most common (at least that I've seen).
    In most cases they seem to follow the convention .[program or binary name]rc
    package.json files can contain external metadata appropriate for config, it just depends on whether or not your project will expect a .rc file or expect it in package.json (or both, as in the case of babel)
    https://stackoverflow.com/questions/36212256/what-are-rc-files-in-nodejs

  • What is monkey patch?
    A monkey patch is a way for a program to extend or modify supporting system software locally (affecting only the running instance of the program).
    Application includes : Replace methods / classes / attributes / functions at runtime, e.g. to stub out a function during testing;
    https://en.wikipedia.org/wiki/Monkey_patch

  • What are githooks and husky?
    Git hooks are scripts that Git executes before or after events such as: commit, push, and receive. Git hooks are a built-in feature - no need to download anything. Git hooks are run locally.

https://githooks.com/

Husky is a JavaScript library that makes Git hooks easier. It offers the possibility of integrating them directly into our JavaScript projects, saving us from having to deal with startup guidelines or startup scripts on repository initialization.

https://medium.com/better-programming/a-complete-pre-commit-workflow-cea6e34f0032

<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}
Enter fullscreen mode Exit fullscreen mode

Globstar allows ** on its own as a name component to recursively match any number of layers of non-hidden directories. Also supported by the JS libraries and Python's glob.

a/*/c    //would match a/b/c, a/f/c, a/c/c etc
a/**/c   //would match a/b/c, a/b/f/t/c
Enter fullscreen mode Exit fullscreen mode

Here it means we want to find the “test” folder, in src folder, but we don’t care where it is located or nested, then we look for any file that has the js, jsx, ts, or tsx extenstion within this folder (which would be our test files).
https://en.wikipedia.org/wiki/Glob_(programming)

  • Typescript vs propTypes?
    Typescript validates types at compile time, whereas PropTypes are checked at runtime.
    If you’re using TS then it’s not necessary to use propTypes, and you can convert them. Read more in this guide
    https://davemn.com/2020-07/typescript-vs-proptypes

  • Compile time vs runtime?
    Compile-time is the time at which the source code is converted into an executable code while the run time is the time at which the executable code is started running. Both the compile-time and runtime refer to different types of error.
    https://stackoverflow.com/questions/846103/runtime-vs-compile-time

2. Unit Testing

  • How do I do UI test if I have different framework or compiler rather than React?
    Try use Dom Testing Library. You need to render the components first before testing. Otherwise you can use the specific library that were built on it, but caters to specific framework or compiler, which will make it easier.
    https://testing-library.com/docs/dom-testing-library/intro

  • Is there any new update on using React Testing Library?
    a. use screen instead of extracting variables from render methods.

// Old way
const { getByTestId } = render(<ResetPasswordForm queryParameters={route} />)
expect(getByTestId('password-input')).toBeEmpty() 
// New way   
render(<ResetPasswordForm queryParameters={route} />)         
expect(screen.getByTestId('password-input')).toBeEmpty()
Enter fullscreen mode Exit fullscreen mode

b. use “userEvent” instead of “fireEvent”, “change” becomes “type” because userEvent mimics real user usage better

// Old way
fireEvent.change(
      getByTestId('email-input'), {
        target: {
          value: brokenEmail
        }
      }
    )
// New way
userEvent.type(
      getByTestId('email-input'), 
      brokenEmail
    )
Enter fullscreen mode Exit fullscreen mode

c. “wait” becomes “waitFor”

d. new mock server is encouraged to be used
https://mswjs.io/

However, this covers only 30% of real accessibility issues and to improve those you have to manually test with assistive technology that real people use (such as screen reader) and involve disabled people in the user research.

  • I got a lot of wrapping in act() warning, how it fix that?
    It depends on your situation. You shouldn’t just simply wrap things in act() to get away with the warning. Read more in this post:
    https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning

  • There seems to be various ways of querying elements, which way is the best?
    There is indeed some priority you should consider when using the query methods. Mostly you should try to mimic the user’s real usage as much as possible. Read more here:
    https://testing-library.com/docs/queries/about/#priority

  • Why I can’t use that getByRole and aria-label to get my password input fields if I toggle between password and text types (so password can be visible)?
    It’s an aria-query error so you have to specify the attribute type in input. In this case you can use data-testid instead.

3. Snapshot and Integration Test

window.history.pushState({}, '', '/search?clientId=client-one');

    customRender(
      <BrowserRouter>
        <Login />
      </BrowserRouter>
    );
Enter fullscreen mode Exit fullscreen mode
  • Why can’t I use the toBeNull() to test elements that should NOT show up in the dom? Now it’s recommended to write like this instead, we use query for things that will NOT be in the dom, and get for things that will be in the dom.
expect(screen.getByRole('alert')).toBeInTheDocument()
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
Enter fullscreen mode Exit fullscreen mode
  • How to debug the network request failed error?
    First check if you set up the test environment properly, and if you’re running the right test script.
    If there are still problems, follow the steps here:
    https://mswjs.io/docs/recipes/debugging-uncaught-requests

  • Test runs alright locally, but getting CI test errors such as network request failure?
    It’s complicated, but it’s likely to be caused by network request related issues. The last time we had this problem it was caused by one of the inline-svg library which doesn’t fetch properly. Also the testing environment should be setting locally for msw to work properly.

  • What is the difference between jest.mock() and MSW?
    Answered by Artem who is the main contributor behind MSW lib:
    jest.mock helps you to mock an entire implementation of something. This means you become in charge of that something, reducing its integrity. That something (i.e. a request library) no longer behaves as it usually does, it listens to your mock and abides it unquestioningly. In the case of API, when you mock axois or fetch, you become in charge of them.
    In NodeJS MSW doesn't mock request clients, but monkey patches request issuing modules. That means that your app still makes a real request, it hits all the logic it should, only to get intercepted by MSW and then give you the control over how to respond to a request.
    I find it fair to compare jest.mock with NodeJS API of MSW, as jest runs in NodeJS. Apart from using MSW with jest and any other testing framework, you can reuse the mocks you write in a browser. In fact, you'd be using identical mocks, no need to rewrite/tweak/configure. MSW is a tool you adopt to be in charge of your network and does so without deviating your app, and it's a tool you can benefit from on many levels: when testing, developing or debugging.

4. E2E Testing

module.exports = {
  root: true,
  plugins: ['eslint-plugin-cypress'],
  extends: ['plugin:cypress/recommended'],
  env: { 'cypress/globals': true },
};
Enter fullscreen mode Exit fullscreen mode
  • How do I configure cypress?
    Check here for options: https://docs.cypress.io/guides/references/configuration.html#Folders-Files

  • The cy.get.('.236r8yf0yyhsleho') with generated class names are annoying, is there any human-friendly way to select those?
    Install @testing-library/cypress in your dependencies, import in the cypress/support/index.js file import '@testing-library/cypress/add-commands, then you can use regex to select text. Since it’s asynchronous we mostly use findByXXXX series.
    Another trick is to add const user = cy, then you will see it from a user perspective instead of cypress robot.

  • How do I avoid the repeated part of the code, such as login, or register?
    You can abstract those into functions and add to Cypress commands in cypress/support/commands.js, then use it in the test such as

 cy.createUser().then( user => { the rest of the cypress tests…})
Enter fullscreen mode Exit fullscreen mode

5. Nodejs Test

  • Any way to improve error message for jest test when there is a function with multiple cases?
    Try abstract it with ​GitHub - atlassian/jest-in-case: Jest utility for creating variations of the same test​

  • What are Spies, Mocks and Stub?
    Spies: Creates fake functions which we can use to track executions. This means we can tell/ find out whether the function has been executed/ how many times its been called etc. We can also use spies on existing functions and get the same capability, to track those functions executions.
    Stubs: Enables us to replace functions. This gives us more control. We can return whatever we want or have our functions work in a way that suites us to be able to test multiple scenarios.
    Mocks: They are fake methods, that have pre-programmed behavior and pre-programmed expectations.

  • Basic introduction to testing with Chai and Sinon?
    How to Test NodeJS Apps using Mocha, Chai and SinonJS​

FAQ

  • Why should I bother with testing?
    To give you more confidence that your app will run smoothly, that your users won’t be angry on weekends while nobody is there to answer phone for customer support and nobody is there to fix the bug.
    It also helps you to focus and think more about your app, its structure, the code robustness etc.

  • How do I cover all the test cases?
    It is advised not to go after 100% coverage but cover the most cases, especially in the UI testing. It is also suggested to use user-centered testing strategy that focuses on testing how the user will use the app, instead of implementation details. If the app pass most tests and it’s running well, you can give it a rest until later you find some edge case.

  • How do I know what to test?
    Probably most asked and most difficult for beginners. Some developers said just to write more tests, explore the options and you will become more experienced. Some said you can see it from a user’s perspective, what’s important for them? How will they use the app? What possible errors they might bump into during their usage, at which stage? What is crucial for the business that failure costs more loss?

  • What are implementation details and why we should not focus on testing on that?
    There are two distinct reasons that it's important to avoid testing implementation details. Tests which test implementation details:
    Can break when you refactor application code. False negatives
    May not fail when you break application code. False positives
    https://kentcdodds.com/blog/testing-implementation-details

  • Why don’t we use Enzyme any more?
    It doesn’t encourage the best practice.
    You can read the above post, and Kent also said : “With shallow rendering, I can refactor my component's implementation and my tests break. With shallow rendering, I can break my application and my tests say everything's still working.”
    https://kentcdodds.com/blog/why-i-never-use-shallow-rendering

  • How to get good at testing quickly?
    There is no quick track, you just have to practice a lot! Mostly it comes from your experiences, so ultimately you just have to write more tests, fail more, and learn from that.
    Documentation

References

Jest - https://jestjs.io/docs/en/getting-started
Jest Cheat Sheet - ​GitHub - sapegin/jest-cheat-sheet: Jest cheat sheet​
Jest Dom - ​GitHub - testing-library/jest-dom: Custom jest matchers to test the state of the DOM​
Testing Library / React Testing Library - https://testing-library.com/docs/
Cypress: https://docs.cypress.io/guides/overview/why-cypress.html#In-a-nutshell
BrowserStack - https://www.browserstack.com/docs/
Applitools - Applitools: Automated Visual Testing with Visual AI

Top comments (0)