Testing
Testing Interview with follow-up questions
1. What is unit testing in Angular?
Unit testing verifies a single unit — a component, service, directive, or pipe — in isolation, with its dependencies mocked, so you can assert its behavior quickly and deterministically.
Angular's tooling: the TestBed sets up a testing module and resolves DI; Jasmine provides the describe/it/expect spec and assertion API; and a runner executes the specs.
TestBed.configureTestingModule({ providers: [{ provide: Api, useValue: apiMock }] });
const svc = TestBed.inject(MyService);
expect(svc.total()).toBe(0);
The current detail that matters: as of Angular 21, Vitest is the default test runner, replacing the now-deprecated Karma. The TestBed/Jasmine-style spec API you write is essentially unchanged, but new projects are scaffolded with Vitest (faster, Vite-powered). A good answer also notes that pure logic (services, pipes, signal-based computeds) often needs no TestBed at all — you can just instantiate and assert.
Follow-up 1
What tools are commonly used for unit testing in Angular?
Some commonly used tools for unit testing in Angular are:
- Jasmine: A behavior-driven development (BDD) framework for writing tests.
- Karma: A test runner that executes tests in various browsers or headless environments.
- TestBed: A utility provided by Angular for configuring and creating instances of components, services, and other dependencies in unit tests.
- Protractor: An end-to-end testing framework for Angular applications.
- Angular Testing Library: A library that provides utilities for testing Angular components in a user-centric way.
Follow-up 2
How do you write a unit test for a service in Angular?
To write a unit test for a service in Angular, you can follow these steps:
- Import the necessary dependencies, including the service to be tested.
- Use the
TestBed.configureTestingModule()method to configure the testing module. - Use the
TestBed.get()method to inject the service into the test. - Write test cases using Jasmine's testing functions, such as
expect()andtoBe(). - Use the
TestBed.createComponent()method to create an instance of the component that uses the service, if needed. - Use the
fixture.detectChanges()method to trigger change detection and update the component's view. - Run the tests using a test runner like Karma.
Here's an example of a unit test for a service in Angular:
import { TestBed } from '@angular/core/testing';
import { MyService } from './my.service';
describe('MyService', () => {
let service: MyService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [MyService]
});
service = TestBed.get(MyService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should return the correct value', () => {
const result = service.getValue();
expect(result).toBe('Hello, World!');
});
});
Follow-up 3
What is TestBed in Angular?
TestBed is a utility provided by Angular for configuring and creating instances of components, services, and other dependencies in unit tests. It allows you to create a testing module that defines the dependencies and providers required for the test. You can use TestBed to inject services, mock dependencies, and configure the testing environment.
Here's an example of using TestBed to create a testing module and inject a service in an Angular unit test:
import { TestBed } from '@angular/core/testing';
import { MyService } from './my.service';
describe('MyService', () => {
let service: MyService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [MyService]
});
service = TestBed.get(MyService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
Follow-up 4
What is the role of 'describe' and 'it' in Angular testing?
In Angular testing, the 'describe' and 'it' functions are provided by the Jasmine testing framework and are used to define test suites and test cases, respectively.
'describe': The 'describe' function is used to define a test suite, which is a group of related test cases. It takes two parameters: a string that describes the test suite and a callback function that contains the test cases.
'it': The 'it' function is used to define a test case, which is an individual unit of testing. It takes two parameters: a string that describes the test case and a callback function that contains the test logic.
By using 'describe' and 'it' functions, you can organize your tests into logical groups and provide descriptive names for each test case.
Here's an example of using 'describe' and 'it' in an Angular unit test:
import { TestBed } from '@angular/core/testing';
import { MyService } from './my.service';
describe('MyService', () => {
let service: MyService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [MyService]
});
service = TestBed.get(MyService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should return the correct value', () => {
const result = service.getValue();
expect(result).toBe('Hello, World!');
});
});
2. What is end-to-end testing in Angular?
End-to-end (E2E) testing exercises the whole application through the UI in a real browser — clicking, typing, and navigating like a user — to verify complete flows (login → dashboard → checkout) across the front end and its APIs.
The crucial 2026 update: Protractor is dead — it was Angular's old E2E tool, deprecated and removed (end-of-life late 2023), and the CLI no longer scaffolds it. Modern Angular E2E uses Cypress or Playwright (both first-class options in ng e2e / ng add), with WebdriverIO as another choice.
// Playwright example
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.fill('[name=email]', '[email protected]');
await page.click('button[type=submit]');
await expect(page).toHaveURL('/dashboard');
});
The framing interviewers want: E2E sits at the top of the testing pyramid — high confidence but slower and more brittle, so keep it focused on critical journeys and push detailed coverage down to unit/component tests. If asked about Protractor specifically, the right answer is that it's retired in favor of Playwright/Cypress.
Follow-up 1
What tools are commonly used for end-to-end testing in Angular?
There are several tools commonly used for end-to-end testing in Angular:
Protractor: Protractor is the official end-to-end testing framework for Angular applications. It is built on top of WebDriverJS and provides a high-level API for interacting with Angular applications.
Cypress: Cypress is a modern JavaScript-based end-to-end testing framework. It provides a fast, reliable, and easy-to-use testing experience with built-in support for Angular applications.
Puppeteer: Puppeteer is a Node.js library that provides a high-level API for controlling headless Chrome or Chromium browsers. It can be used for end-to-end testing of Angular applications by simulating user interactions and capturing screenshots.
These tools offer various features and capabilities for writing and executing end-to-end tests in Angular applications.
Follow-up 2
How does end-to-end testing differ from unit testing?
End-to-end testing and unit testing are two different types of testing that serve different purposes:
Scope: End-to-end testing focuses on testing the entire application stack, including the user interface, backend services, and database interactions. Unit testing, on the other hand, focuses on testing individual units of code, such as functions or classes, in isolation.
Dependencies: End-to-end testing requires all the components of an application to be up and running, including external dependencies such as databases or APIs. Unit testing, on the other hand, can be done in isolation without the need for external dependencies.
Speed: End-to-end testing is typically slower than unit testing because it involves testing the entire application stack. Unit testing, on the other hand, is faster because it focuses on testing small units of code.
Both end-to-end testing and unit testing are important for ensuring the quality of an application, but they serve different purposes and are used at different stages of the development process.
Follow-up 3
What are some challenges you might face when conducting end-to-end testing?
Conducting end-to-end testing in Angular applications can pose several challenges:
Test setup: Setting up the test environment for end-to-end testing can be complex, especially when dealing with external dependencies such as databases or APIs.
Test data: Generating and managing test data for end-to-end testing can be challenging, especially when dealing with large datasets or complex data structures.
Test stability: End-to-end tests can be sensitive to changes in the application, such as UI changes or backend service changes. This can lead to test failures even when the application is functioning correctly.
Test execution time: End-to-end tests can take a long time to execute, especially when testing complex scenarios or large datasets. This can slow down the development process and increase the feedback loop.
Test maintenance: End-to-end tests can be difficult to maintain, especially when the application undergoes frequent changes. Test scripts may need to be updated or rewritten to accommodate these changes.
Despite these challenges, end-to-end testing is an important part of the testing process and can help ensure the overall quality and reliability of an Angular application.
3. How can you mock dependencies in Angular tests?
You replace a real dependency with a fake via Angular's DI in TestBed.configureTestingModule({ providers }), overriding the token so the unit under test receives the mock:
const apiSpy = jasmine.createSpyObj('Api', ['getUser']);
apiSpy.getUser.and.returnValue(of({ name: 'Ada' }));
TestBed.configureTestingModule({
providers: [{ provide: Api, useValue: apiSpy }],
});
Common techniques: spy objects (jasmine.createSpyObj, or vi.fn() under Vitest) for stubbing methods and asserting calls; useValue/useClass to swap a whole implementation; and provideHttpClientTesting() with HttpTestingController to mock HTTP without hitting the network.
The current note: with Vitest now the default runner (v21+), you'd use vi.fn()/vi.spyOn() instead of Jasmine spies, but the DI-override approach is identical. Also, for plain classes you can skip TestBed and pass mock dependencies straight into the constructor — simpler and faster when no Angular wiring is needed.
Follow-up 1
What is the purpose of mocking dependencies?
The purpose of mocking dependencies in Angular tests is to isolate the component or service under test from its dependencies. By providing mock implementations for the dependencies, you can control their behavior and ensure that the tests focus only on the specific functionality of the component or service. Mocking dependencies also allows you to simulate different scenarios and edge cases, making it easier to test the component or service in isolation.
Follow-up 2
What are some common ways to mock services in Angular tests?
There are several common ways to mock services in Angular tests:
Using jasmine.createSpyObj(): This function allows you to create a mock object with predefined methods and behaviors. You can use it to create a mock service with the same API as the real service.
Providing a mock service: You can provide a mock service directly in the testing module by using the providers array. This allows you to define a custom implementation for the service.
Using the TestBed.overrideProvider() method: This method allows you to override the provider of a service with a mock implementation. You can use it to replace the real service with a mock service.
Follow-up 3
How do you handle asynchronous operations when testing in Angular?
When testing asynchronous operations in Angular, you can use the async() and fakeAsync() functions provided by the TestBed. These functions allow you to write tests that involve asynchronous code in a synchronous manner.
async(): This function is used to wrap a test function that contains asynchronous operations. It ensures that the test function waits for all asynchronous operations to complete before finishing.
fakeAsync(): This function is used to write tests that involve timers, intervals, and other asynchronous operations that can be controlled by the fakeAsync() function. It allows you to simulate the passage of time and control the execution of asynchronous code.
Both async() and fakeAsync() provide the tick() function, which is used to advance the virtual clock and trigger the execution of pending asynchronous operations.
4. What is the purpose of Karma and Jasmine in Angular testing?
Historically these were Angular's two testing tools, with different jobs:
- Jasmine — the testing framework: the
describe/it/expectsyntax, matchers, and spies you write tests with. - Karma — the test runner: it launches real browsers, loads your specs, runs them, and reports results.
So Jasmine defines what the tests are; Karma executes them in a browser.
The essential 2026 update: Karma is deprecated (the project stopped accepting new features and bug fixes) and, as of Angular 21, Vitest is the default runner for new projects, executing tests in a fast Vite/Node environment instead of spinning up browsers. Jasmine-style specs still work, but new setups often use Vitest's own API. So a current answer is: "Jasmine was the spec/assertion framework and Karma the browser test runner — but Karma is now deprecated and Vitest is the modern default runner."
Follow-up 1
What are some features of Karma?
Karma offers several features that make it a powerful tool for testing Angular applications:
Browser Compatibility: Karma allows you to run your tests in multiple browsers, ensuring cross-browser compatibility.
Continuous Integration: Karma integrates well with popular CI tools like Jenkins and Travis CI, allowing you to automate your testing process.
Test Coverage: Karma provides code coverage reports, which help you identify areas of your code that are not adequately covered by tests.
Test Debugging: Karma allows you to debug your tests directly in the browser, making it easier to identify and fix issues.
Test Watcher: Karma can watch your files for changes and automatically rerun the tests, providing fast feedback during development.
These features make Karma a versatile and efficient tool for testing Angular applications.
Follow-up 2
What are some features of Jasmine?
Jasmine offers several features that make it a popular choice for testing Angular applications:
BDD Syntax: Jasmine provides a clean and readable syntax for defining test cases and assertions. It uses keywords like
describe,it,expect, andbeforeEachto structure and write tests.Spies: Jasmine allows you to create spies, which are functions that can track calls, arguments, and return values. Spies are useful for testing function calls and interactions between different parts of your code.
Matchers: Jasmine provides a wide range of matchers that allow you to write expressive and precise assertions. Matchers like
toEqual,toContain, andtoThrowhelp you validate the expected behavior of your code.Asynchronous Testing: Jasmine provides built-in support for testing asynchronous code. It offers functions like
doneandasyncto handle asynchronous operations and ensure that your tests wait for the expected results.
These features make Jasmine a powerful and flexible framework for testing Angular applications.
Follow-up 3
How do they work together in the context of Angular testing?
In the context of Angular testing, Karma and Jasmine work together seamlessly to provide a comprehensive testing solution. Here's how they work together:
Configuration: You configure Karma to use Jasmine as the testing framework in the Karma configuration file (
karma.conf.js). This tells Karma to load the Jasmine framework and use its syntax and structure for writing tests.Test Execution: Karma launches the browsers specified in the configuration file and captures them. It then runs the test files specified in the configuration file, using the Jasmine framework to execute the tests.
Test Results: Karma collects the test results from the browsers and reports them in the terminal or the browser. It provides detailed information about the test suites, test cases, and assertions, including any failures or errors.
By combining the capabilities of Karma and Jasmine, you can write and execute tests for your Angular application, ensuring its quality and reliability.
5. How do you test Angular components?
Use TestBed to create the component, render it, interact, and assert on the rendered DOM and class state:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CounterComponent], // standalone component
providers: [{ provide: Api, useValue: apiMock }],
});
});
it('increments on click', () => {
const fixture = TestBed.createComponent(CounterComponent);
fixture.detectChanges(); // initial render
fixture.nativeElement.querySelector('button').click();
fixture.detectChanges(); // reflect the change
expect(fixture.nativeElement.textContent).toContain('1');
});
The flow: configure the module (importing the standalone component and mocking deps) → createComponent → detectChanges() to render → act → detectChanges() again → assert.
Current best practices to mention: call fixture.componentRef.setInput('x', value) to set signal inputs; use fixture.whenStable()/await (or fakeAsync/tick) for async work; and consider @testing-library/angular for user-centric queries. As of Angular 21 these specs run on Vitest by default (Karma deprecated). Avoid asserting on internal implementation — test rendered output and behavior.
Follow-up 1
What is the role of ComponentFixture in testing Angular components?
The ComponentFixture class is a utility provided by the Angular Testing Framework. It is used to create and interact with instances of Angular components during testing.
The ComponentFixture class provides methods and properties that allow you to:
Access the component instance: You can use the
componentInstanceproperty to get a reference to the component instance and access its properties and methods.Access the component's DOM element: You can use the
nativeElementproperty to get a reference to the component's DOM element. This allows you to interact with the component's view and test user interactions.Trigger change detection: You can use the
detectChangesmethod to trigger change detection and update the component's view. This is necessary when you want to test how the component reacts to changes in its inputs or internal state.Emit events: You can use the
triggerEventHandlermethod to simulate user interactions and emit events from the component. This allows you to test how the component responds to user actions.
Follow-up 2
How do you test component methods and properties?
To test component methods and properties, you can use the componentInstance property of the ComponentFixture class.
Here are the steps to test component methods and properties:
Create an instance of the component using the
TestBed.createComponentmethod.Access the component instance using the
componentInstanceproperty of theComponentFixture.Call the component methods or access the component properties.
Assert the expected behavior by using assertions with the
expectfunction from Jasmine.
For example, if you have a component with a method calculateSum that takes two numbers as input and returns their sum, you can test it like this:
it('should calculate the sum correctly', () => {
const fixture = TestBed.createComponent(MyComponent);
const component = fixture.componentInstance;
const result = component.calculateSum(2, 3);
expect(result).toBe(5);
});
Follow-up 3
How do you test user interactions like clicking a button or entering text in a form field?
To test user interactions like clicking a button or entering text in a form field, you can use the triggerEventHandler method of the ComponentFixture class.
Here are the steps to test user interactions:
Create an instance of the component using the
TestBed.createComponentmethod.Access the component instance using the
componentInstanceproperty of theComponentFixture.Use the
triggerEventHandlermethod to simulate user interactions and emit events from the component.Assert the expected behavior by using assertions with the
expectfunction from Jasmine.
For example, if you have a component with a button that increments a counter when clicked, you can test it like this:
it('should increment the counter when the button is clicked', () => {
const fixture = TestBed.createComponent(MyComponent);
const component = fixture.componentInstance;
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(component.counter).toBe(1);
});
Live mock interview
Mock interview: Testing
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.