-1

I want to write unit testing using Jest for my Node.js functions and need to test if response object has some specific keys or not. Something like this:

expect(await mokChannelsService.getChannel(inputDto)).toEqualKeys(outputDto);

// Or just a normal function

const result = await mokChannelsService.getChannel(inputDto);
const equal = isEqualKeys(result, outputDto);

This toEqualKeys function should check equality just for keys not values. And these objects are not instances of a class. for example these two objects should be equal:

const object1 = {
  names: {
    firstName: '',
    lastName: '',
  },
  phone: 1,
};

const object2 = {
  names: {
    firstName: 'John',
    lastName: 'Due',
  },
  phone: 1234567890,
};

const object3 = {
  names: {
    firstName: 'John',
    lastName: 12,
  },
  mobile: '12345',
};

const result1 = toEqualKeys(object1, object2); // true
const result1 = toEqualKeys(object1, object3); // false

Is there any NPM packages available for this?

rostamiani
  • 2,859
  • 7
  • 38
  • 74
  • 1
    What are you actually testing? Why don't you know more specifically what you'll get from the function? Or why not use existing matchers, e.g. `expect(result1).toMatchObject({ phone: expect.any(String), ... })` (which would also give _substantially_ better diagnostics in a failing case than "expected false to be true")? – jonrsharpe Sep 12 '22 at 07:45
  • You are right, I know what to expect most of the time but sometimes I don't know the exact output and just care about output format – rostamiani Sep 12 '22 at 07:50
  • That _partially_ addresses (or is related to, at least, if not giving any actual information) one third of the comment. – jonrsharpe Sep 12 '22 at 10:03

1 Answers1

0

You can iterate each of the keys in object1, checking that the same key exists in object2 and - if the associated property in object1 is an object, that all the keys from that object also exist in a similar object in object2:

const object1 = {
  names: {
    firstName: '',
    lastName: '',
  },
  phone: 1,
};

const object2 = {
  names: {
    firstName: 'John',
    lastName: 'Due',
  },
  phone: 1234567890,
};

const object3 = {
  names: {
    firstName: 'John',
    lastName: 12,
  },
  mobile: '12345',
};

const toEqualKeys = (obj1, obj2) => Object.keys(obj1)
  .every(k => k in obj2 && 
    (typeof obj1[k] != 'object' || typeof obj2[k] == 'object' && toEqualKeys(obj1[k], obj2[k]))
  )
  
console.log(toEqualKeys(object1, object2))
console.log(toEqualKeys(object1, object3))

You can get more detail on the differences by logging differences into an array (using reduce to iterate the keys in the reference object). This function will return an array of errors (empty if there are none) for each comparison:

const object1 = {
  names: {
    firstName: '',
    lastName: '',
  },
  phone: 1,
};

const object2 = {
  names: {
    firstName: 'John',
    lastName: 'Due',
  },
  phone: 1234567890,
};

const object3 = {
  names: {
    firstName: 'John',
    lastName: 12,
  },
  mobile: '12345',
};

const object4 = {
  names: 'John',
  mobile: '12345',
};

const object5 = {
  names: {
    firstName: 'John',
    lastName: 12,
  },
  phone: { 
    landline : '12345',
    mobile : '555'
  }
};

const object6 = {
  names: {
    firtName: 'John',
    lastName: 'Due',
  },
  phone: 1234567890,
};


const notEqualKeys = (obj1, obj2, prefix = '') => Object.keys(obj1)
  .reduce((acc, k) => {
    if (!(k in obj2)) acc.push(`key ${k} not present in obj2${prefix}`)
    if (typeof obj1[k] == 'object') {
      if (typeof obj2[k] == 'object') {
        acc = acc.concat(notEqualKeys(obj1[k], obj2[k], `${prefix}.${k}`))
      }
      else {
        acc.push(`${k} is an object in obj1${prefix} but a value in obj2${prefix}`)
      }
    }
    else if (typeof obj2[k] == 'object') {
      acc.push(`${k} is a value in obj1${prefix} but an object in obj2${prefix}`)
    }
    return acc
  }, [])
  
console.log((arr = notEqualKeys(object1, object2)).length ? arr : true)
console.log((arr = notEqualKeys(object1, object3)).length ? arr : true)
console.log((arr = notEqualKeys(object1, object4)).length ? arr : true)
console.log((arr = notEqualKeys(object1, object5)).length ? arr : true)
console.log((arr = notEqualKeys(object1, object6)).length ? arr : true)
Nick
  • 138,499
  • 22
  • 57
  • 95
  • @rostamiani you should unaccept this; it doesn't answer the "using Jest" part of your question – Nick Sep 12 '22 at 07:58
  • The question title is "How to compare two objects". And i can call your function in my test body. `expect(isEqualKeys(ob1,ob2).toBe(true)`. Then this answer is correct – rostamiani Sep 12 '22 at 08:36
  • 1
    For one thing you get exactly the problem I mentioned above - the output from the test failure doesn't give you any clue what the problem is. By flattening the value to a boolean then asserting on that you lose any useful diagnostics. Also this doesn't include the key _types_, although that's only mentioned in the title then apparently forgotten about. – jonrsharpe Sep 12 '22 at 10:03
  • @jonrsharpe You are right, maybe I will not use this function for testing. But will not remove the question. Maybe this function will be used somewhere else. Thanks – rostamiani Sep 12 '22 at 12:34
  • @rostamiani without the testing aspect this is just a dupe of e.g. https://stackoverflow.com/q/41802259/3001761 – jonrsharpe Sep 12 '22 at 12:38
  • @jonrsharpe thanks for the feedback. FWIW I've added a second function to the answer which gives a detailed list of differences in the second object relative to the keys of the first object. Note that I don't think you can compares types of *keys*; `typeof Object.keys({ 1 : 'a'})[0]` returns `string` just as `typeof Object.keys({ '1' : 'a'})[0]` does. Or am I misinterpreting that? – Nick Sep 12 '22 at 23:43