Follow this article to setup jest in angular -
Unit tests are valuable in software development for the following reasons:
Guard against Breaking Changes: They help ensure that new code changes do not inadvertently break existing functionality. Reveal Design mistakes - Tests act as a feedback mechanism for your system’s design. When writing tests, if you find it hard to set up dependencies, mock objects, or trigger certain behaviors, it often means your code is tightly coupled, overly complex, or not well-structured. Isolated Tests
Run class logic without Angular (TestBed not used). Just new the class and test its methods. Non-Isolated Tests
Run with Angular’s TestBed + DI + lifecycle + templates. Use createComponent + fixture.detectChanges().
Difference b/w Jasmine + Karma and Jest
Jest - A JavaScript testing framework for unit, integration, and snapshot testing.
Why better for JS testing:
Zero configuration for most projects (works out-of-the-box). Built-in mocking and spying support. Supports async testing and Promises easily. Fast, parallel test execution. Snapshot testing for UI components (React). Good test coverage reporting. Snapshot Testing - Captures the rendered output of a component or function and saves it in a .snap file.
Purpose: Detects unintended changes over time. Render component → create snapshot. Future tests compare output to saved snapshot. Test fails if output differs. Use case: Mostly used in React components or UI testing. toBe()
Type: Strict equality matcher (uses ===) Use case: For primitives (numbers, strings, booleans) test('number equality', () => {
expect(2 + 2).toBe(4); // ✅ passes
});
toEqual()
Type: Deep equality matcher Use case: For objects and arrays test('object equality', () => {
expect({a: 1}).toEqual({a: 1}); // ✅ passes
});
Note: Compares values recursively, not references.
toBeNull matches only null
toBeUndefined matches only undefined
toBeDefined is the opposite of toBeUndefined
toBeTruthy matches anything that an if statement treats as true
toBeFalsy matches anything that an if statement treats as false
For floating point equality, use toBeCloseTo instead of toEqual
test('adding floating point numbers', () => {
const value = 0.1 + 0.2;
//expect(value).toBe(0.3); This won't work because of rounding error
expect(value).toBeCloseTo(0.3); // This works.
});
If you want to test whether a particular function throws an error when it's called, use toThrow.
function compileAndroidCode() {
throw new Error('you are using the wrong JDK!');
}
test('compiling android goes as expected', () => {
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow(Error);
// You can also use a string that must be contained in the error message or a regexp
expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
expect(() => compileAndroidCode()).toThrow(/JDK/);
// Or you can match an exact error message using a regexp like below
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK$/); // Test fails
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK!$/); // Test pass
});
Suppose you have a function that uses a callback:
function fetchData(callback) {
setTimeout(() => {
callback("data received");
}, 100);
}
You want to test that the callback is called with the correct argument.
Jest Test:
test("fetchData calls callback with correct data", (done) => {
function callback(data) {
try {
expect(data).toBe("data received");
done(); // tells Jest the async test is finished
} catch (error) {
done(error);
}
}
fetchData(callback);
});
Explanation:
done is used because the callback runs asynchronously. The expect checks that the callback receives the right argument. If the expectation fails, done(error) reports the failure. Mock Functions (Jest)
Purpose: Replace real functions with fake functions to test interactions, calls, and return values without running the actual implementation. Also called: Spies (can “spy” on function calls). const mockFn = jest.fn();
mockFn('Hello');
expect(mockFn).toHaveBeenCalled(); // ✅ function was called
expect(mockFn).toHaveBeenCalledWith('Hello'); // ✅ called with 'Hello'
const mockFn = jest.fn(() => 42);
expect(mockFn()).toBe(42);
jest.mock('./myModule'); // Replace entire module with mocks
Use Cases
Test function calls without executing real code. Track how many times a function was called. Test different return values without changing original code. Spying
Definition: Watching a function to see how it was called, without changing its behavior. Purpose: Verify function calls, arguments, return values, and call count. const math = {
add: (a, b) => a + b
};
const spy = jest.spyOn(math, 'add'); // Spy on math.add
math.add(2, 3);
expect(spy).toHaveBeenCalled(); // ✅ function was called
expect(spy).toHaveBeenCalledWith(2, 3); // ✅ called with correct args
Optional: Mock implementation while spying spy.mockImplementation(() => 10);
expect(math.add(2, 3)).toBe(10);
Difference Between Mock & Spy
Spy Restore
Definition: Restores a spied-on function to its original implementation. Purpose: Clean up after spying so other tests aren’t affected. Example
const math = {
add: (a, b) => a + b
};
// Spy on math.add
const spy = jest.spyOn(math, 'add').mockImplementation(() => 10);
expect(math.add(2, 3)).toBe(10); // ✅ returns mocked value
// Restore original function
spy.mockRestore();
expect(math.add(2, 3)).toBe(5); // ✅ returns original value