0

I have created a function that generates an array of anonymous functions based on string of rules given as the parameter. However, my test are failing:

$ jest -t "unit/validationRules generates correct validation rules"
 FAIL  test/unit/composables/validationRules.spec.js
  ● unit/validationRules › generates correct validation rules

    expect(received).toEqual(expected) // deep equality

    Expected: [[Function anonymous], [Function anonymous], [Function anonymous]]
    Received: serializes to the same string

      11 |     ]
      12 |
    > 13 |     expect(rules).toEqual(expected)
         |                   ^
      14 |   })
      15 | })
      16 |

      at Object.<anonymous> (test/unit/composables/validationRules.spec.js:13:19)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |       0 |        0 |       0 |       0 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 2 skipped, 1 of 3 total
Tests:       1 failed, 5 skipped, 6 total     
Snapshots:   0 total
Time:        7.779 s
Ran all test suites with tests matching "unit/validationRules generates correct validation rules".
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

My implementation look like this

tests/unit/composables/validationRules.spec.js

import { useRules } from '~/composables'

describe('unit/validationRules', () => {
  it('generates correct validation rules', () => {
    const rules = useRules('required|string|max:255')

    const expected = [
      (x) => !!x || 'This field is required',
      (x) => typeof x === 'string' || 'The given value must be a string',
      (x) => (!!x || '').length <= 255 || 'A maximum of 255 characters allowed',
    ]

    expect(rules).toEqual(expected)
  })
})

composables/validationRules.js

/**
 * Creates validation rules based on the given string of rules.
 *
 * You can set multiple rules by chaining each using `|` (pipe)
 * character, including conditional rule using `:`.
 *
 * Sample usage: `required|string|max:255`
 *
 * @param {String} rules Rules to be used for input validation.
 * @returns {Array<Function<String|Boolean>>}
 */
export const useRules = (rules) => {
  const splitRules = rules.toLowerCase().split('|')

  const generatedRules = []
  for (let i = 0; i < splitRules.length; i++) {
    const rule = splitRules[i]

    if (rule.includes(':')) {
      const conditions = rule.split(':')

      if (conditions.length < 2) {
        continue
      }

      const key = conditions[0]
      const val = conditions[1]

      if (key === 'max') {
        generatedRules.push(
          (x) =>
            (!!x || '').length <= val ||
            `A maximum of ${val} characters allowed`
        )
      }
    } else {
      if (rule === 'required') {
        generatedRules.push((x) => !!x || 'This field is required')
      }

      if (rule === 'string') {
        generatedRules.push(
          (x) => typeof x === 'string' || 'The given value must be a string'
        )
      }
    }
  }

  return generatedRules
}

There is similar question to this but I think my case is different since I don't want to get the return value of the anonymous functions and wanted to compare the function instead.

Can you guys help me?

Yura
  • 1,937
  • 2
  • 19
  • 47

1 Answers1

1

Depending on what you want to test with "function equality", there are multiple scenarios. The only reasonable ways in my opinion are (1) and (2), the rest is for the curious reader.

  1. Test some specific inputs

    For instance, for (x) => !!x || 'This field is required', you could test expect(rules[0](undefined)).toEqual('This field is required) and expect(rules[0](123).toEqual(true). This does not cover all cases, but should cover the most important ones.

  2. Don't create the function inline

    If you use functions like createMaxRule(max) instead of an inline function, you could mock them with jest to return the expected function and afterwards check it for equality. In this case, rules[0] === expected[0] returns true, because they are the same function (not only equivalent).

  3. Compare the function definition

    If you know that the test function and the implementation are defined in the same way, you could convert them to strings and compare them, e.g. rules[0].toString() === expected[0].toString(). This will break as soon as a function is implemented differently, even if they behave the same way

  4. Prove the equality

    I don't know if this is possible in JavaScript and I would never use this in a unit test. But you could also automatically reason about all possible inputs and use a prover that verifies the equivalence of the functions

  5. Go through all possible inputs

    If the input set is limited (e.g. for a boolean with only true, false) you could go through all possible inputs and check if the output is the same. However, this is impossible for e.g. strings, as there are infinite string values

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
A_A
  • 1,832
  • 2
  • 11
  • 17