Jest/Vitest Testing Fundamentals
Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Getting Started
Common Matchers
Basic Testing Concepts
Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Getting Started
Common Matchers
Basic Testing Concepts
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 aboveon the right), 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?