Code Testing & Analysis

What we'll cover:

  • eslint
  • typescript
  • intro to testing
    • TDD & BDD

How do I know that my code is written well?

The big question:

Linting

  • Originally it meant 'removing that bad bits of wool from a sheep's fleece'
  • Developed as a tool for C languages
  • It is a static code analysis tool
  • Douglas Crockford created JSLint to give JavaScript programmers the ability to check their code for bad presentation and poor coding choices.
  • JSLint was notoriously harsh - it even said in the input box 'Warning: JSLint will hurt your feelings'
  • JSHint is a fork of jslint. It was then born because jslint  was not customisable enough. JSHint was popular because it explained to you WHY what you'd written was bad, and was less dismissive
  • eslint is a plugin for node that allows the checks to be run as part of the build process
  • Atom: linter has an eslint plugin

Configuring

Ignoring Files

Like most systems there is an:

  • ignore file (.eslintignore) uses similar syntax to .gitignore
    • node_modules and bower_modules are ignored by default
# Ignore built files except build/index.js
build/*
!build/index.js

Globbing

Globbing is a way to write a string that represent s a search pattern in files to be processed. e.g. it give instructions as to which files to affect and which to ignore

 

All files in CWD
Files of a type in CWD

Recursively search down

Not these files

 

*.*
*.css
**/*.js
!*.min.js

Configuring

  • config file (dot file)
    • although this can be built into package.json
    • old file was .eslintrc
    • new version is either eslint.json, .js or .yaml
    • You can have multiple config files

What you can configure:

  1. Environments (node, browser)
  2. Parser (the program which interprets the file, e.g. http://Babel-ESLint)
  3. Globals (things you get via the global scope)
  4. Rules ( prefer-arrow-functions, eqeqeq, etc.)

 

Full Guide: https://eslint.org/docs/user-guide/configuring

Example in package.json

"eslintConfig": {
    "parser": "babel-eslint",
    "extends": "airbnb",
    "plugins": [
      "react",
      "jsx-a11y",
      "import"
    ],
    "env": {
      "browser": true,
      "node": true,
      "jest": true,
      "es6": true
    },
    "rules": {
      "global-require": 0,
      "no-underscore-dangle": 0,
      "no-console": 0,
      "react/jsx-filename-extension": [
        1,
        {
          "extensions": [
            ".js",
            ".jsx"
          ]
        }
      ],
      "import/no-extraneous-dependencies": [
        "error",
        {
          "devDependencies": true
        }
      ]
    },
    "globals": {
      "__CLIENT__": true,
      "__SERVER__": true,
      "__DISABLE_SSR__": true,
      "__DEV__": true,
      "webpackIsomorphicTools": true
    }
  },
  "stylelint": {
    "extends": "stylelint-config-standard",
    "rules": {
      "string-quotes": "single",
      "selector-pseudo-class-no-unknown": [
        true,
        {
          "ignorePseudoClasses": [
            "global",
            "local"
          ]
        }
      ]
    }
  },

Create a config file

$ eslint --init

Rules (and controlling them)

/* global var1, var2 */

Top of file inline annotations (globals)

Enable & disable inline commands (All rules)

/* eslint-disable */
  alert('not checked here');
/* eslint-enable */

Enable & disable inline commands (Some rules)

/* eslint-disable no-alert */
  alert('not checked here');
/* eslint-enable no-alert */

Enable & disable inline commands (rules levels)

/* eslint eqeqeq: "off", curly: "off" */
if (a == b) return; // no errors

Rule Levels

  • "off" or 0 - turn the rule off
  • "warn" or 1 - turn the rule on as a warning (doesn’t affect exit code)
  • "error" or 2 - turn the rule on as an error (exit code is 1 when triggered)
/* eslint quotes: ["error", "double"], curly: 2 */

Sidenote: Prettier

  • Often used with eslint, prettier looks at your code formatting
  • 'Is it laid out neatly'
  • https://prettier.io/
  • It will auto-format for you
    • if you have it set as the default formatter and have 'format-on-save' selected
  • It has an 'rc' config file too, where you put options
  • It is best practice to use it with a .editorconfig file (link)

Sidenote: StyleLint

  • Lints your CSS
  • Docs

Static Type Checking

What is 'Static Type Checking'?

  • JavaScript is a loosely typed language
  • This can cause problems: 0 == false, for example!
  • Whilst it's difficult to catch errors that occur at 'run-time' (i.e. errors that happen when the code is running), it is easy to look over the code and see if what we've written is prone to error due to mismatched or unexpected  types
  • A static type checker is:
    • Static - because it's testing your code when it's stationary, not running
    • type-checker - because it focuses on whether they types you have used will work for your expected output

Example

function greet(name) {
    return 'Hello ' + name;
}

What if greet is called and name not passed?

// Hello undefined

 

We need some way to see:

  1. What types of arguments are acceptable
  2. What will happen if those arguments are optional
  3. What will the function return

 

Solution

function greet(name:string) {
    return 'Hello ' + name; // returns a String
}

const greeting1 = greet(); // Error
const greeting2 = greet(true); // Error
const greeting3 = greet('Fred'); // Works!

What if we could put comments (or 'annotations') that would show what we expected things to be!

We need to be able to annotate our code to show how we expect the flow of data to work

Typescript

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

 

presentation

How do I know that my code [still] works?

The big question:

  Testing!

Realistically

Credit: Kent C. Dodds

Unit Testing

  • Unit testing is the bedrock of all good apps
  • You are testing the smallest piece of functionality that you can
  • N.B. JavaScript is a B*tch to test because of:
    • async
    • closures
    • new (or any 'behind the scenes' process)
  • There are two styles:
    • Test Driven Development (TDD):
      • Checking functionality only
    • Behaviour Driven Development (BDD):
      • Where you're testing according to a business case

How to Unit Test

  • Unit testing is sometimes called red/green development
  • You start of with an expectation that is failing
    • N.B. Most tests will pass if you leave them empty!!
  • You then write the minimum amount of code required to pass that test
  • You then continue to write another expectation
  • etc., etc.
  • If tests are included in another test, you may delete a test

What does a test look like?

import greet from './modules/greet';

describe('Greet function', () => {

    it('responds with the correct greeting', (done) => {
      expect(greet('James')).toBe('Hello James');
    });

    it('throws an error if no name is provided', (done) => {
      expect(() => {
        greet();
      }).toThrow('No name provided');
    });

});

Vitest

  • We're going to skip Jest and use vitest instead
  • docs

Jest

Matchers

https://facebook.github.io/jest/docs/en/using-matchers.html

  • Are a key part of unit testing. They let you test
    • if something exists
    • if it is what you expected it to be

Watching your code...

  • Jest, like many others, has a CLI (docs)
  • You can launch jest with the word jest on the command line
  • It will run and report once
  • IF you run it with the --watch flag, then it will continue to watch your files
  • You can provide options to ignore certain directories via  a configuration file (jest.config.js):
module.exports = {
  testPathIgnorePatterns: ['e2e-small-demo/cypress'],
};

Setup/Teardown

beforeAll(() => {
  connectDB();
});

beforeEach(() => {
  initializeCityDatabase();
});

afterEach(() => {
  clearCityDatabase();
});

afterAll(() => {
  disconnectDB();
});

Mocking with Jest (or Sinon)

  • Allow you to
    • remove difficult bits of functionality (like server calls)
    • watch functions
  • Come in 3 types
    • Stub - blocks the path to the function and [optionally] returns some controlled data
    • Spy - watches a function for how many times it's called. It doesn't stop the function call
    • Mocks - actually create an entity that behaves like the thing you'd be contacting/reading from
  • See mocks in https://jestjs.io/docs/en/mock-functions

React Testing

Coverage

  • There is a question of: "How much of my code base has been tested?"
  • There is a program called 'Istanbul' (docs)
  • Jest (and others) use this program to generate a 'code coverage report'
  • You can see one by running any jest command in the CLI with the --coverage flag added
  • You can specify whether this is output to the terminal or to a series of HTML pages, etc. by specifying a 'coverage reporter'
  • You read it like this
  • It should look like (see next page)
  • % Stmts: % of statements (e.g. let x = 4;) executed
  • % Lines: % of lines ran (let x=4, y=2; <-- 1 line, 2 statements) 
  • % Branch: Has each branch (also called DD-path) of each control structure (such as in if and case statements) been executed during testing?
  • % functions: % of functions executed during testing

End-to-End (aka e2e) Testing

What is e2e testing?

  • We let a machine take our website for a drive
  • https://www.cypress.io/
  • It allows you to say things like:

    "Go to this URL, then click the login link. Now find the login form and type (like a human) these details and press submit. Now what URL are you on? Are you logged in? Can you see the extra menu items?"

Visual Testing

Visual Testing

  • Another type of testing is 'Visual Testing'
  • Here we take snapshots in different browsers; at different screen sizes; on different devices, etc.
  • You can use a cloud service like BrowserStack (paid solution) to do this
    • There is a library percy.io that is also well loved