Testing with Negation (.not)

When writing tests, you will often need to assert that a value matches an expected result and that it does not match an unexpected one.

expect(1).not.toBe(2);

In this lesson, we will dive into the different ways you can use negation in your Jest and Vitest tests.

Simple example of .not

The .not modifier will only pass if the matcher after it fails.

Example:

test('should not be the same reference', () => {
  const user1 = {
    name: 'Alice',
    age: 25,
  };
  const user2 = {
    name: 'Alice',
    age: 25,
  };

  expect(user1).not.toBe(user2); // Different objects in memory
  expect(5).not.toBe(10);
  expect('hello').not.toBe('world');
});

You can use these with any matcher, such as toEqual():

test('should not be deeply equal', () => {
  const user1 = {
    name: 'Alice',
    age: 25,
  };
  const user2 = {
    name: 'Bob',
    age: 30,
  };

  expect(user1).not.toEqual(user2);
  expect([1, 2, 3]).not.toEqual([
    1, 2, 4,
  ]);
  expect({ a: 1, b: 2 }).not.toEqual({
    a: 1,
    b: 3,
  });
});

Testing that errors are not thrown

This is a common pattern, if you want to be explicit about checking that a fn didn't throw

(realistically: if it threw, the function would fail anyway. But a test with .not.toThrow() shows intention of how it should work)

function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}

test('divide should throw error for zero divisor', () => {
  expect(() => divide(10, 0)).toThrow();
  expect(() =>
    divide(10, 2)
  ).not.toThrow(); // Should not throw for valid input

  // Note: in a real test, you would probably just assert the return value like this:
  // expect(divide(10,2)).toBe(5)
});

Common use cases

The .not modifier is commonly used for:

Checking absence of elements:

test('element should not exist', () => {
  const items = [
    'apple',
    'banana',
    'orange',
  ];
  expect(items).not.toContain('grape');
});

Verifying objects don't have properties:

test('object should not have property', () => {
  const user = {
    name: 'Alice',
    age: 25,
  };
  expect(user).not.toHaveProperty(
    'email'
  );
});

Asserting values are not null/undefined:

const calculateSomething = () => 1 + 1;

test('value should be defined', () => {
  const result = calculateSomething();
  expect(result).not.toBeNull();
  expect(result).not.toBeUndefined();
});

Warning - dealing with false positives

Be careful! Using .not can lead to false positives if you're not testing what you think you're testing.

It is a very easy mistake to write a test with .not that will always pass.

test('false positive example', () => {
  const user = { name: 'Alice' };

  // This passes, but not for the reason you might think!
  // It passes because 'username' property doesn't exist at all
  expect(user.username).not.toBe('Bob'); // !! passes because user.username is undefined

  // Better: explicitly check for undefined first
  expect(user.username).toBeUndefined();
});
test('another false positive', () => {
  const element =
    document.querySelector(
      '.non-existent'
    );

  // This always passes because element is null
  expect(element?.textContent).not.toBe(
    'Hello'
  ); // !! Always passes!

  // Better: check existence first
  expect(element).toBeNull();
});

A good way to get around this is using each() (covered in detail in an upcoming lesson), where you use the same test to test both the 'true' and 'false' path

// Testing user permissions - ensures the property exists and works correctly
describe('user permissions', () => {
  test.each([
    {
      user: {
        name: 'Alice',
        role: 'admin',
      },
      hasAdminAccess: true,
    },
    {
      user: {
        name: 'Bob',
        role: 'user',
      },
      hasAdminAccess: false,
    },
    {
      user: {
        name: 'Charlie',
        role: 'guest',
      },
      hasAdminAccess: false,
    },
  ])(
    '$user.name with role $user.role should have admin access: $hasAdminAccess',
    ({ user, hasAdminAccess }) => {
      // This ensures we're testing a real property that exists
      if (hasAdminAccess) {
        expect(user.role).toBe('admin');
      } else {
        expect(user.role).not.toBe(
          'admin'
        );
      }

      // The test would fail if user.role was undefined,
      // protecting us from false positives
    }
  );
});

// Another example: testing array contents
describe('shopping cart items', () => {
  const cart = [
    'apple',
    'banana',
    'orange',
  ];

  test.each([
    {
      item: 'apple',
      shouldContain: true,
    },
    {
      item: 'banana',
      shouldContain: true,
    },
    {
      item: 'grape',
      shouldContain: false,
    },
    {
      item: 'watermelon',
      shouldContain: false,
    },
  ])(
    'cart should contain $item: $shouldContain',
    ({ item, shouldContain }) => {
      if (shouldContain) {
        expect(cart).toContain(item);
      } else {
        expect(cart).not.toContain(
          item
        );
      }
    }
  );
});

Tips

I'd recommend avoiding .not if possible.

Quite often instead of .not, the test can be clearer and easier to maintain if you just give the value it should be

const loginUser = () => ({
  id: 1,
  status: 'active',
});

// Less clear - what should it actually be?
test('user status after login', () => {
  const user = loginUser();
  expect(user.status).not.toBe(
    'inactive'
  );
});

// Better - explicit about expected values
test('user status after login', () => {
  const user = loginUser();
  expect(user.status).toBe('active'); // << much better, easier to read
});

Lesson Task

In the test runner (use the tabs above), use .not.toBe('strong') to make the test assertions pass.

Tip: ensure the value you assert with exists (e.g. is defined) before negating to avoid false positives.

Ready to try running tests for this lesson?