Skip to content

Latest commit

 

History

History
319 lines (241 loc) · 8.54 KB

readme.md

File metadata and controls

319 lines (241 loc) · 8.54 KB

🧐 cynic

async testing framework for es modules

  • cynic is designed to be dirt-simple, because i'm sick of overcomplicated testing frameworks
  • the test suites are just nested async functions
  • the whole framework is just simple es modules that run anywhere: node, browser, puppeteer, deno
  • no magic assumptions are made about or foisted onto the environment: the assertion library and everything else is just simply imported like from any other module
  • examples here are shown in typescript, but of course you can use vanilla js

let's get cynical, and make a damn test suite!

  1. install cynic into your project

    npm install --save-dev cynic
  2. write a test suite, example.test.ts

    import {Suite, assert, expect} from "cynic"
    
    export default <Suite>{
      "alpha system": {
        "can sum two numbers (boolean return)": async() => {
          const a = 1
          const b = 2
          // no assertion library required:
          // simply returning false, or throwing, will fail a test
          return (a + b) === 3
        },
        "can sum three numbers (assert)": async() => {
          const a = 1
          const b = 2
          const c = 3
          // benefits of 'assert'
          //  - you get a stack trace
          //  - you can provide a custom message for each failure
          assert((a + b + c) === 6, `sum is wrong`)
        }
      },
      "bravo system": {
        "can multiply numbers (expect)": async() => {
          const a = 2
          const b = 3
          // benefits of 'expect'
          //  - you get a stack trace
          //  - cynic tries to invent a message about the failure
          expect(a * b).equals(6)
          expect(a * b * a).equals(12)
        }
      }
    }

now run it!

  • you can run the suite file through the cynic cli

    # run your tests in node
    cynic node example.test.js
    
    # run your tests in browser
    cynic browser example.test.js
    
    # run your tests in puppeteer (headless browser)
    cynic puppeteer example.test.js
    
    # use node debugger
    node inspect node_modules/cynic/dist/cli.js node example.test.js

    cynic executes the default export as a test suite

    optional arguments for all runtimes:

    • --label="test suite" — the report title

    optional arguments for browser and puppeteer runtimes:

    • --open=false — true to prompt open your default browser
    • --port=8021 — run the server on a different port
    • --origin="http://localhost:8021" — connect to the server via an alternative url (mind the port number!)
    • --cynic-path=node_modules/cynic — use an alternative path to the cynic library's root
    • --importmap-path=./dist/importmap.json — provide an import map for your test suites

    if puppeteer isn't running properly, see puppeteer's troubleshooting.md

  • or you can just execute your test suite, manually, anywhere

    this should work anywhere you can import an es module

    import {test} from "cynic"
    import suite from "./example.test.js"
    
    ;(async() => {
    
      // run the test suite
      const {report, ...stats} = await test("example suite", suite)
    
      // emit the report text to console
      console.log(report)
    
      // handle results programmatically
      if (stats.failed === 0) console.log("done")
      else console.log("failed!")
    
      // returns stats about the test run results
      console.log(stats)
    
    })()

    see which stats are available in the Stats interface in types.ts

so what do the console reports look like?

  • report: successful run

    cynic example suite
    
      ▽ examples
        ▽ alpha system
          ✓ can sum two numbers (boolean return)
          ✓ can sum three numbers (assertion)
        ▽ bravo system
          ✓ can multiply numbers (expectation)
    
    0 failed tests
    0 thrown errors
    3 passed tests
    3 total tests
    0.00 seconds
    
  • report: a test returns false
    return false to indicate a failed test

    cynic example suite
    
      ▽ examples
        ▽ alpha system
    
    ═════ ✘ can sum two numbers (boolean return)
    
        ▽ bravo system
    
    ✘ can sum two numbers (boolean return) — failed
    
    1 FAILED tests
    0 thrown errors
    2 passed tests
    3 total tests
    0.00 seconds
    
  • report: a test throws
    a thrown string or error will be shown as the failure reason

    cynic example suite
    
      ▽ examples
        ▽ alpha system
    
    ═════ ✘ can sum two numbers (boolean return)
    ――――――― arithmetic failed for interesting reasons
    
        ▽ bravo system
    
    ✘ can sum two numbers (boolean return) — arithmetic failed for interesting reasons
    
    1 FAILED tests
    1 thrown errors
    2 passed tests
    3 total tests
    0.00 seconds
    
  • report: a test fails an assertion
    assertions will display a stack trace, and optional custom message

    cynic example suite
    
      ▽ examples
        ▽ alpha system
    
    ═════ ✘ can sum three numbers (assertion)
    ――――――― CynicBrokenAssertion: sum is wrong
              at assert (file:///work/cynic/dist/assert.js:7:15)
              at can sum three numbers (assertion) (file:///work/cynic/dist/internals/example.test.js:13:20)
              at execute (file:///work/cynic/dist/internals/execute.js:13:34)
              [...]
    
        ▽ bravo system
    
    ✘ can sum three numbers (assertion) — CynicBrokenAssertion: sum is wrong
    
    1 FAILED tests
    1 thrown errors
    2 passed tests
    3 total tests
    0.00 seconds
    
  • report: a test fails an expectation
    stack trace is provided, and a failure reason is generated automatically

    cynic example suite
    
      ▽ examples
        ▽ alpha system
        ▽ bravo system
    
    ═════ ✘ can multiply numbers (expectation)
    ――――――― CynicBrokenExpectation: expect(7).equals(6): not equal, should be
              at composite (file:///work/cynic/dist/expect.js:46:19)
              at Object.equals (file:///work/cynic/dist/expect.js:25:125)
              at can multiply numbers (expectation) (file:///work/cynic/dist/internals/example.test.js:20:39)
              at execute (file:///work/cynic/dist/internals/execute.js:13:34)
              [...]
    
    ✘ can multiply numbers (expectation) — CynicBrokenExpectation: expect(7).equals(6): not equal, should be
    
    1 FAILED tests
    1 thrown errors
    2 passed tests
    3 total tests
    0.00 seconds
    

hot tips for big brains

  • use object nesting to group and organize tests arbitrarily

    import {Suite} from "cynic"
    export default <Suite>{
      "nested tests": {
        "more nested": {
          "exceedingly nested": {
            "it works": async() => true
          }
        }
      }
    }
  • you can just throw strings as assertions

    import {Suite} from "cynic"
    export default <Suite>{
      "assertions and expectations": async() => {
        const example = "abc"
    
        // let's call it "the spartan assertion"
        if (!example.includes("b"))
          throw `expected example to include "b"`
    
        return true
      }
    }
  • or you can use the handy assert function to do that, you get stack traces

    import {Suite, assert} from "cynic"
    export default <Suite>{
      "using 'assert'": async() => {
        const example = "abc"
        assert(example === "abc", `example must equal "abc"`)
        assert(example.includes("b"), `example should include "b"`)
      }
    }
  • or you can also use the experimental new expect api, you get auto-generated messages and stack traces

    import {Suite, expect} from "cynic"
    export default <Suite>{
      "using 'expect'": async() => {
        const example = "abc"
        expect(example).defined()
        expect(example).equals("abc")
      }
    }
  • a suite or test can return another suite or test — easy setups!

    export default <Suite>(async() => {
    
      // doing some async setup
      const myFile = await loadFile("myfile.json")
    
      // returning more tests
      return {
        "group of tests": {
          "my file exists": async() => {
            return !!myFile
          }
        }
      }
    })

food for thought

  • 🥃 chase moskal made this with open source love. please contribute!