javascriptjestjsmatcher

Jest: expect object not to have property


I want to write a test that asserts a given object does not have certain properties.

Say I have a function

function removeFooAndBar(input) {
  delete input.foo;
  delete input.bar;
  return input;
}

Now I want to write a test:

describe('removeFooAndBar', () => {
  it('removes properties `foo` and `bar`', () => {
    const data = {
      foo: 'Foo',
      bar: 'Bar',
      baz: 'Baz',
    };
    expect(removeFooAndBar(data))
      .toEqual(expect.objectContaining({
        baz: 'Baz', // what's left
        foo: expect.not.exists() // pseudo
        bar: undefined // this doesn't work, and not what I want
      }));
  });
});

What's the proper way to assert this?


Solution

  • Update after the discussion in the comments

    You can use expect.not.objectContaining(). This approach works fine but has one unfortunate edge case: It matches when the property exists, but is undefined or null. To fix this you can explicitly add those values to be included in the check. You need the jest-extended package for the toBeOneOf() matcher.

    expect({foo: undefined}).toEqual(expect.not.objectContaining(
        {foo: expect.toBeOneOf([expect.anything(), undefined, null])}
    ));
    

    An example with nested props that fails:

    const reallyAnything = expect.toBeOneOf([expect.anything(), undefined, null]);
    
    expect({foo: undefined, bar: {baz: undefined}}).toEqual(
        expect.not.objectContaining(
            {
                foo: reallyAnything,
                bar: {baz: reallyAnything},
            }
        )
    );
    

    Original answer

    What I'd do is to explicitly check whether the object has a property named bar or foo.

    delete data.foo;
    delete data.bar;
    delete data.nested.property; 
    
    expect(data).not.toHaveProperty('bar');
    expect(data).not.toHaveProperty('foo');
    expect(data.nested).not.toHaveProperty('property');
    // or
    expect(data).not.toHaveProperty('nested.property');
    

    Or make this less repeating by looping over the properties that will be removed.

    const toBeRemoved = ['foo', 'bar'];
    
    toBeRemoved.forEach((prop) => {
        delete data[prop];
        expect(data).not.toHaveProperty(prop);
    });
    

    However, the loop approach isn't too great for possible nested objects. I believe what you are looking for is expect.not.objectContaining()

    expect(data).toEqual(expect.not.objectContaining({foo: 'Foo', bar: 'Bar'}));
    

    expect.not.objectContaining(object) matches any received object that does not recursively match the expected properties. That is, the expected object is not a subset of the received object. Therefore, it matches a received object which contains properties that are not in the expected object. - Jest Documentation