Search code examples
javascriptangularjasminekarma-webpack

How to test two returned functions with different argument value in javascript?


I have a function that returns comparisonFunction

  getComparisonFunction(propertyOfComparison) {
    const func = function(a, b){
      if ( a[propertyOfComparison] < b[propertyOfComparison] ) {
        return -1;
      }
      if ( a[propertyOfComparison] > b[propertyOfComparison] ) {
        return 1;
      }
      return 0;
    };

    return func;
  }

This method will be used inside javascript "sort" method. for example:

arrayOfObjects.sort(getComparisonFunction('name'));

This method will sort "arrayOfObjects" by "name" property. Method works fine, question is: how i can compare function call with different arguments

  it('should get correct comparison function', function () {
    const func = component.getComparisonFunction('testProperty');

    const expectedFunc = function(a, b){
      if ( a['testProperty'] < b['testProperty'] ) {
        return -1;
      }
      if ( a['testProperty'] > b['testProperty'] ) {
        return 1;
      }
      return 0;
    };

    expect(func.toString()).toEqual(expectedFunc.toString());
  });

This is what i have now, but it doesn't works. The error i am receiving after running the code is:

 Expected 'function (a, b) {
                if (a[propertyOfComparison] < b[propertyOfComparison]) {
                    return -1;
                }
                if (a[propertyOfComparison] > b[propertyOfComparison]) {
                    return 1;
                }
                return 0;
            }' to equal 'function (a, b) {
                if (a['testProperty'] < b['testProperty']) {
                    return -1;
                }
                if (a['testProperty'] > b['testProperty']) {
                    return 1;
                }
                return 0;
            }'.

Solution

  • Checking the code of the function as a test very brittle and can break easily giving you a false negative:

    let someFn = function(a, b) {
      return a + b;
    }
    
    let expected = `function(a, b) {
      return a + b;
    }`
    
    console.log("Test original implementation:", test(someFn.toString(), expected));
    
    //later the code style is changed to remove extra whitespace and make it one line
    someFn = function(a, b) { return a+b; }
    
    console.log("Test updated implementation:", test(someFn.toString(), expected));
    
    //simple testing
    function test(expected, actual) {
      return expected == actual
    }

    Just making non-functional changes to the code breaks the test.

    Worse yet, if there are functional changes to the code, the test cannot guarantee that the new implementation behaves like the old one, since it only looks at the structure of the code:

    //simplified case of what the actual code could be doing
    function someCodeBaseFunction() {
      let someInput = [8, 12, 42];
      return someFn(...someInput)
    }
    
    let someFn = function(a, b) { return a+b; }
    
    let expected = `function(a, b) { return a+b; }`
    
    console.log("Test original implementation:", test(someFn.toString(), expected));
    
    console.log("Codebase usage:", someCodeBaseFunction()); //20, as the third number is ignored
    
    //new implementation
    someFn = function(...args) { 
      return args.reduce((a, b) => a + b); 
    }
    
    //update the test, so it passes
    expected = `function(...args) { 
      return args.reduce((a, b) => a + b); 
    }`
    
    console.log("Test updated implementation:", test(someFn.toString(), expected));
    
    //some existing line of code
    console.log("Codebase usage:", someCodeBaseFunction()); //62, as the third number is now used
    
    //simple testing
    function test(expected, actual) {
      return expected == actual
    };

    Instead, what you want to do it test the behaviour of the code and set your expectations there. That way, if the implementation changes, you can make sure that the implementation still conforms to the same set of expectations.

    In this case, you need to create a sample input that is initially unordered, try to order it and then expect that the order worked as you expected. In pseudo-code that would look a bit like this:

    //arrange
    input = [
     {testProperty: "c", id: 1},
     {testProperty: "a", id: 2},
     {testProperty: "d", id: 3},
     {testProperty: "b", id: 4}
    ];
    
    expected = [
     {testProperty: "a", id: 2},
     {testProperty: "b", id: 4},
     {testProperty: "c", id: 1},
     {testProperty: "d", id: 3}
    ];
    
    //act
    input.sort(component.getComparisonFunction('testProperty'))
    
    //assert
    expect(input).toEqual(expected);
    

    You can also add more tests at a more granular level to bind the expectations even more, if you want. For example, if you want to ensure that the comparison is case-sensitive

    //arrange
    a = { testProperty: "a" };
    b = { testProperty: "B" };
    
    //act
    result = component.getComparisonFunction('testProperty')(a, b)
    
    //assert
    expect(result).toBeGreaterThanOrEqual(1)
    

    Or case-insensitive:

    //arrange
    a = { testProperty: "a" };
    b = { testProperty: "B" };
    
    //act
    result = component.getComparisonFunction('testProperty')(a, b)
    
    //assert
    expect(result).toBeLessThanOrEqual(-1)
    

    This defines your expectations much more clearly and makes sure that future changes will cover exactly what you need.