o-check-list

3.1.0 • Public • Published

Checklist

Validations, assertions and tests for conditions on objects

Installation

npm install o-check-list

Documentation

http://o-programming-language.org/

Validations

Validate that an object satisfies one or more conditions.

If a validation fails it raises a ConditionCheckFailure

const { validation } = require('o-check-list')

const user = new User()

validation((validate) => {
  validate.that(user).isNotNull()
  validate.that(user.name).isNotBlank()
})

Get a validation result instead of raising an error with validate.whether

const { validation } = require('o-check-list')

const user = new User()

validation((validate) => {
  validate.whether(user).isNotNull()
  validate.whether(user.name).isNotBlank()
})

validate.whether does not use try/catch

Assertions

Assertions are like validations with the option to be globally disabled

const { GlobalAssertion } = require('o-check-list')

GlobalAssertion.disableAssertions()
GlobalAssertion.enableAssertions()
GlobalAssertion.isAssertionsEnabled()

For instance, to disable assertions in a production environment do

const { GlobalAssertion } = require('o-check-list')

if (process.env.NODE_ENV === 'production') {
  GlobalAssertion.disableAssertions()
}

If assertions are disabled the assertion block is not evaluated

If assertions are enabled they behave the same as validations do

const { assertion } = require('o-check-list')

const user = new User()

validation((assertion) => {
  assertion.that(user).isNotNull()
  assertion.that(user.name).isNotBlank()
})

Assertions are usually part of a development and debugging process, whereas validations are part of the program logic

Tests

Test stateful objects with sequencial steps, organized as one or more use cases of a functional story

const { story } = require('o-check-list')

story('Removes an element from a Set', (story) => {
  story.useCase('Remove an existing element from a Set', (exec) => {
    let set

    exec.step('let be an empty set', () => {
      set = new Set()
    })

    exec.step('then add an element', () => {
      set.add('item')
    })

    exec.step('then the element is present', (assure) => {
      assure.that('item').isIncludedIn(set)
    })

    exec.step('then remove the element', () => {
      set.delete('item')
    })

    exec.step('then the element is not present', (assure) => {
      assure.that('item').isNotIncludedIn(set)
    })
  })

  story.useCase('Remove an absent element from a Set', (exec) => {
    let set

    exec.step('let be an empty set', () => {
      set = new Set()
    })

    exec.step('then the element is not present', (assure) => {
      assure.that('item').isNotIncludedIn(set)
    })

    exec.step('then remove the element', () => {
      set.delete('item')
    })

    exec.step('then the element is not present', (assure) => {
      assure.that('item').isNotIncludedIn(set)
    })
  })
})

Async tests

Because of its sequential nature, rather than a declarative nature, testing asynchronous code in o-check-list is pretty much the same as testing synchronous code

Add exec.beAsync() at the beginning of each async story.useCase, and that's it

E.g.

const { story } = require('o-check-list')

story('Call an asynchronous method', (story) => {
  story.useCase('Fetch data from a remote server', (exec) => {
    let remoteServer
    let fetchedData

    exec.beAsync()

    exec.step('Given a RemoteServer', () => {
      remoteServer = new RemoteServer()
    })

    exec.step('then, fetch ask it for data', async () => {
      fetchedData = await remoteServer.fetchData()
    })

    exec.step('then, data is as expected', (assure) => {
      assure.that( fetchedData ).equals( 'something' )
    })
  })
})

If you ever forget to flag the story as exec.beAsync(), yet some step is asynchronous, o-check-list will kindly remember you to add it

Filtering tests

To test only one or a few stories or useCases from the whole suite, include any number of alone() and ignore() statements in any story or useCase

const { story } = require('o-check-list')

story('Removes an element from a Set', (story) => {
  story.useCase('Remove an existing element from a Set', (exec) => {
    exec.alone() // <-- ignore all other useCases not flagged with .alone() as well

    let set

    exec.step('let be an empty set', () => {
      set = new Set()
    })

    exec.step('then add an element', () => {
      set.add('item')
    })

    exec.step('then the element is present', (assure) => {
      assure.that('item').isIncludedIn(set)
    })

    exec.step('then remove the element', () => {
      set.delete('item')
    })

    exec.step('then the element is not present', (assure) => {
      assure.that('item').isNotIncludedIn(set)
    })
  })

  story.useCase('Remove an absent element from a Set', (exec) => {
    let set

    exec.step('let be an empty set', () => {
      set = new Set()
    })

    exec.step('then the element is not present', (assure) => {
      assure.that('item').isNotIncludedIn(set)
    })

    exec.step('then remove the element', () => {
      set.delete('item')
    })

    exec.step('then the element is not present', (assure) => {
      assure.that('item').isNotIncludedIn(set)
    })
  })
})

Collecting test results

Stories are regular javascript objects, they can be collected and run in any regular javascript context

Create a test and get the result of its evaluation

const { GlobalStories } = require('o-check-list')

const stories = GlobalStories.createStoriesContext()

const checks = stories.story('Removes an element from a Set', (story) => {
  story.useCase('Remove an existing element from a Set', (exec) => {
    let set

    exec.step('let be an empty set', () => {
      set = new Set()
    })

    exec.step('then add an element', () => {
      set.add('item')
    })

    exec.step('then the element is present', (assure) => {
      assure.whether('item').isIncludedIn(set)
    })

    exec.step('then remove the element', () => {
      set.delete('item')
    })

    exec.step('then the element is not present', (assure) => {
      assure.whether('item').isNotIncludedIn(set)
    })
  })

  story.useCase('Remove an absent element from a Set', (exec) => {
    let set

    exec.step('let be an empty set', () => {
      set = new Set()
    })

    exec.step('then the element is not present', (assure) => {
      assure.whether('item').isNotIncludedIn(set)
    })

    exec.step('then remove the element', () => {
      set.delete('item')
    })

    exec.step('then the element is not present', (assure) => {
      assure.whether('item').isNotIncludedIn(set)
    })
  })
})

const checkResults = checks.evaluate()
checkResults.isValid() === true

Custom checks

Custom checks are subclasses of ConditionCheck

const ConditionCheck = require('o-checklist')

class IsString extends ConditionCheck {
  getCheckId () {
    return 'isString'
  }

  evaluateConditionOn ({ subject, params, result, evaluationContext }) {
    if (typeof (subject) === 'string') { return }
    const subjectString = this.displayString(subject)
    result.beNotValid({
      reason: `Expected a string, got ${subjectString}`
    })
  }
}

module.exports = IsString

Add custom checks globally, in a story or in a use case with registerCheck method

const { story, GlobalStories } = require('o-check-list')

const isStringChecker = new IsString()

// Add the custom checker for all stories
GlobalStories.registerCheck({ checkMethodName: 'isString', conditionChecker: isStringChecker })

story('...', (story) => {
  // or only for this story
  story.registerCheck({ checkMethodName: 'isString', conditionChecker: isStringChecker })

  story.useCase('...', (exec) => {
    // or only for this useCase
    story.registerCheck({ checkMethodName: 'isString', conditionChecker: isStringChecker })

  })
})

Exceptions are optional

All checks in o-check-list, validations, assertions and tests, share the same underlaying implementation

The implementation can run without using exceptions at all with the use of .whether(...) instead of .that(...)

Before and after execution blocks

To perform setup/tearDown actions, before and after each UseCase execution, do

GlobalStories.beforeEachExecution( () => {
  setupSomething()
})

GlobalStories.afterEachExecution( () => {
  tearDownSomething()
})

story('...', (story) => {
  story.beforeEachExecution( () => {
    setupSomething()
  })

  story.afterEachExecution( () => {
    tearDownSomething()
  })

  story.useCase('...', (exec) => {
    exec.beforeEachExecution( () => {
      setupSomething()
    })

    exec.afterEachExecution( () => {
      tearDownSomething()
    })
  })
})

At the moment, there are no beforeAllExecution, afterAllExecution methods, for it seems to be more on the test runner protocol, rather than on the definition of the tests

Run tests command

o-check-list has a limited test runner

To run tests in the directory ./tests and ./examples within the project, execute

npx checklist

To run tests in a directory different than the default one, execute

npx checklist testsDirectory ./tests

To filter tests based on their description, execute

npx checklist testsDirectory ./tests filter "Removes an element from a Set"

Since o-check-list is regular javascript objects and methods, it's faily possible to implement a custom runner, both in Node.js and Browser sides, though

DoMe commands

DoMe commands are intended to be self-documented, please take a look at the files in DoMe/forDevelopment/inWindows

Package Sidebar

Install

npm i o-check-list

Weekly Downloads

1

Version

3.1.0

License

ISC

Unpacked Size

104 kB

Total Files

84

Last publish

Collaborators

  • haijindev