Angular unit testing interview questions and answers
Angular unit testing interview questions and answers
1. What is unit testing in Angular?
Answer:
Unit testing in Angular refers to the practice of testing individual units of
code (usually components, services, or other classes) in isolation to ensure
that they behave as expected. Angular provides tools like Jasmine (testing
framework) and Karma (test runner) to help with writing and running unit tests.
Unit tests ensure that the code is modular, maintainable, and performs
correctly under various conditions.
2. What are the tools commonly used for unit testing in
Angular?
Answer:
The main tools used for unit testing in Angular are:
- Jasmine:
A behavior-driven development framework for testing JavaScript code. It
provides functions like describe(), it(), and expect() to define and
assert test cases.
- Karma:
A test runner that works with Jasmine and other testing frameworks. Karma
runs the tests in a real browser or headless browser and provides results
in the command line.
- TestBed:
A testing utility provided by Angular that allows you to configure and
initialize the Angular environment for testing. It is used to create test
modules and components.
- Protractor: An end-to-end testing framework for Angular applications, but for unit testing, it is not typically used.
3. How do you test an Angular component?
Answer:
To test an Angular component, you typically use TestBed to configure the
testing environment, create the component instance, and trigger change
detection. Here’s an example:
import { ComponentFixture, TestBed } from
'@angular/core/testing';
import { MyComponent } from './my-component.component';
describe('MyComponent', () => {
let component:
MyComponent;
let fixture:
ComponentFixture<MyComponent>;
beforeEach(async ()
=> {
await
TestBed.configureTestingModule({
declarations: [
MyComponent ]
})
.compileComponents();
});
beforeEach(() =>
{
fixture =
TestBed.createComponent(MyComponent);
component =
fixture.componentInstance;
fixture.detectChanges(); // triggers change detection
});
it('should create',
() => {
expect(component).toBeTruthy();
});
});
- TestBed
configures the module for testing.
- ComponentFixture
is used to interact with the component instance and trigger change
detection.
4. What is the purpose of async and fakeAsync in Angular
testing?
Answer:
- async:
This function is used when you need to work with asynchronous operations
(like HTTP requests or timers). It waits for all promises to resolve
before running the test.
- fakeAsync:
Used for handling asynchronous code with Angular's zone.js. It
allows you to simulate the passage of time and control asynchronous
operations, such as setTimeout or Promise resolution, synchronously.
Example of using async:
it('should fetch data asynchronously', async () => {
const data = await
service.getData();
expect(data).toBeDefined();
});
Example of using fakeAsync:
it('should call setTimeout', fakeAsync(() => {
let value = 0;
setTimeout(() =>
{
value = 1;
}, 1000);
tick(1000); //
Simulates the passage of 1000 ms
expect(value).toBe(1);
}));
5. What is a spy in Jasmine, and how do you use it in
Angular testing?
Answer:
A spy is a function that tracks calls to another function, allowing you to mock
the behavior or verify that a function has been called. In Angular unit
testing, spies are commonly used to mock services, HTTP requests, or other
dependencies.
Example:
it('should call the fetchData method', () => {
const service =
jasmine.createSpyObj('MyService', ['fetchData']);
service.fetchData.and.returnValue(of('mock data')); // Mock return value
component.getData();
expect(service.fetchData).toHaveBeenCalled();
});
- createSpyObj()
creates a mock object with a spy for each method you want to track.
- and.returnValue()
is used to mock the return value of the spied method.
6. What is the role of beforeEach() in Angular unit
testing?
Answer:
beforeEach() is a Jasmine function that runs before each test in a describe()
block. It is typically used for setup tasks like configuring modules, creating
components, and initializing data. It ensures that the tests are isolated and
have a clean state.
Example:
beforeEach(() => {
// Initialize
variables, mock services, or configure TestBed
fixture =
TestBed.createComponent(MyComponent);
component =
fixture.componentInstance;
});
7. What is the difference between shallow and deep
testing in Angular?
Answer:
- Shallow
Testing: In shallow testing, only the component under test is created,
and its child components are mocked or not rendered. It is faster and more
isolated, focusing on testing the component itself without worrying about
the child components.
- Deep
Testing: In deep testing, the entire component tree, including child
components and templates, is rendered and tested. This is useful when you
want to test the full integration of a component, including its
interactions with child components.
Example of shallow testing:
TestBed.configureTestingModule({
declarations: [
MyComponent ]
});
Example of deep testing:
TestBed.configureTestingModule({
declarations: [
MyComponent, ChildComponent ]
});
8. How do you test HTTP requests in Angular?
Answer:
You can test HTTP requests by using the HttpClientTestingModule and HttpTestingController.
This allows you to mock HTTP requests and verify that they are made correctly.
Example:
import { HttpClientTestingModule, HttpTestingController }
from '@angular/common/http/testing';
describe('MyService', () => {
let httpMock:
HttpTestingController;
let service:
MyService;
beforeEach(() =>
{
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule ],
providers: [
MyService ]
});
service =
TestBed.inject(MyService);
httpMock =
TestBed.inject(HttpTestingController);
});
it('should fetch
data', () => {
const mockData = {
name: 'Test Data' };
service.getData().subscribe(data => {
expect(data).toEqual(mockData);
});
const req =
httpMock.expectOne('http://api.example.com/data');
expect(req.request.method).toBe('GET');
req.flush(mockData);
});
afterEach(() => {
httpMock.verify();
});
});
- expectOne()
checks that there is only one HTTP request for the given URL.
- req.flush()
is used to provide a mock response to the HTTP request.
- httpMock.verify()
ensures no outstanding HTTP requests after the test.
9. How do you test form validation in Angular?
Answer:
To test form validation in Angular, you can interact with the form control
properties and test their behavior based on user input. You can check whether
the form is valid or invalid and whether the correct validation error messages
appear.
Example:
it('should mark the form as invalid when required fields are
empty', () => {
component.form.controls['name'].setValue('');
component.form.controls['email'].setValue('');
expect(component.form.invalid).toBeTruthy();
});
10. How do you mock a service in Angular unit tests?
Answer:
In Angular unit tests, you can mock services using Jasmine spies or by creating
a mock class. You can inject the mock service into the component to simulate
the behavior of the real service.
Example using a spy:
it('should call the fetchData method of the service', ()
=> {
const mockService =
jasmine.createSpyObj('MockService', ['fetchData']);
mockService.fetchData.and.returnValue(of({ data: 'mock' }));
const component =
new MyComponent(mockService);
component.ngOnInit();
expect(mockService.fetchData).toHaveBeenCalled();
});
Example using a mock class:
class MockService {
fetchData() {
return of({ data:
'mock' });
}
}
beforeEach(() => {
TestBed.configureTestingModule({
providers: [{
provide: MyService, useClass: MockService }]
});
});
11. How do you test an Angular directive?
Answer:
Testing Angular directives involves setting up a testing module that declares
the directive, then interacting with an element to check the expected behavior
(e.g., DOM manipulation, event handling). You can use DebugElement to interact
with the directive in the DOM.
Example:
it('should apply the correct class on mouseover', () => {
const element =
fixture.debugElement.query(By.css('div'));
const nativeElement
= element.nativeElement;
// Simulate
mouseover event
nativeElement.dispatchEvent(new Event('mouseover'));
fixture.detectChanges();
// Check if the
class is added
expect(nativeElement.classList.contains('highlight')).toBe(true);
});
12. How do you test a pipe in Angular?
Answer:
Testing pipes in Angular is straightforward as you just pass input values
through the pipe and check the output.
Example:
it('should transform the input value correctly', () => {
const pipe = new
MyCustomPipe();
expect(pipe.transform('hello')).toBe('HELLO');
});
13. What is the difference between beforeEach and afterEach
in Angular testing?
Answer:
· beforeEach:
This function is executed before each test case in a describe block. It is
commonly used for setup tasks like initializing services, components, or mock
data.
· afterEach:
This function is executed after each test case. It is typically used for
cleanup tasks such as verifying that no HTTP requests are left pending or
clearing up mock data.
Example:
beforeEach(() => {
// Setup tasks
(e.g., creating component, initializing services)
});
afterEach(() => {
// Cleanup tasks
(e.g., verifying HTTP requests, resetting spies)
});
14. What is the purpose of TestBed.configureTestingModule()
in Angular unit testing?
Answer:
TestBed.configureTestingModule() is used to configure the testing environment
for your unit tests. It is where you define the components, services, and other
dependencies that will be used in the tests. This method prepares everything
that Angular needs to instantiate the components or services during testing.
Example:
beforeEach(() => {
TestBed.configureTestingModule({
declarations:
[MyComponent], // Declare components
used in the test
providers:
[MyService] // Provide services or
dependencies
});
});
15. How do you test a service method that makes an HTTP
call in Angular?
Answer:
You can use the HttpClientTestingModule and HttpTestingController
to mock HTTP requests and verify that they are being made correctly. The HttpTestingController
allows you to intercept the HTTP requests and provide a mock response.
Example:
import { HttpClientTestingModule, HttpTestingController }
from '@angular/common/http/testing';
describe('MyService', () => {
let service:
MyService;
let httpMock:
HttpTestingController;
beforeEach(() =>
{
TestBed.configureTestingModule({
imports:
[HttpClientTestingModule],
providers:
[MyService]
});
service =
TestBed.inject(MyService);
httpMock =
TestBed.inject(HttpTestingController);
});
it('should fetch
data via HTTP GET', () => {
const mockData = {
name: 'John' };
service.getData().subscribe(data => {
expect(data).toEqual(mockData);
});
const req =
httpMock.expectOne('http://api.example.com/data');
expect(req.request.method).toBe('GET');
req.flush(mockData);
});
afterEach(() => {
httpMock.verify(); // Verify that
there are no outstanding requests
});
});
16. What is the role of tick() in Angular testing, and
when should it be used?
Answer:
tick() is used in conjunction with fakeAsync to simulate the passage of
time for asynchronous tasks (such as setTimeout, setInterval, or promises). It
allows you to advance the virtual clock by a specified number of milliseconds,
triggering the completion of timers or delayed code.
Example:
it('should call setTimeout after 1 second', fakeAsync(()
=> {
let value = 0;
setTimeout(() =>
{ value = 1; }, 1000);
tick(1000); // Simulate passage of time
expect(value).toBe(1);
}));
When to use tick(): Use it when you need to handle
asynchronous operations that depend on timers, such as setTimeout or setInterval.
17. What is the difference between ComponentFixture.detectChanges()
and ComponentFixture.whenStable() in Angular testing?
Answer:
· detectChanges():
It triggers Angular's change detection and updates the component’s view. You
use this when you want to explicitly update the view after changing the state
of the component (e.g., updating properties).
· whenStable():
It is an asynchronous method that waits for all asynchronous tasks (such as
HTTP requests or setTimeout calls) to finish. You typically use this method
when you have asynchronous operations in your component that affect the view.
Example:
it('should update the view after async operation', async(()
=> {
component.fetchData();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.data).toBeDefined();
});
}));
Use detectChanges() when you want to trigger change
detection manually. Use whenStable() to wait for asynchronous operations to
complete.
18. How do you test custom Angular pipes?
Answer:
To test custom pipes, you can directly instantiate the pipe and test its
transformation method. You can pass different input values and assert the
output.
Example:
import { MyCustomPipe } from './my-custom.pipe';
describe('MyCustomPipe', () => {
let pipe:
MyCustomPipe;
beforeEach(() =>
{
pipe = new
MyCustomPipe();
});
it('should transform
value correctly', () => {
expect(pipe.transform('test')).toBe('TEST');
});
});
This tests the transform() method of the custom pipe and
ensures it behaves as expected.
19. How do you test an Angular component’s lifecycle
methods (e.g., ngOnInit, ngOnChanges)?
Answer:
You can test Angular component lifecycle methods by invoking them and checking
their behavior. For example, to test ngOnInit, you can trigger it by creating a
component instance and calling detectChanges() to execute Angular's lifecycle
hooks.
Example:
it('should call ngOnInit', () => {
const spyOnInit =
spyOn(component, 'ngOnInit');
component.ngOnInit();
expect(spyOnInit).toHaveBeenCalled();
});
For ngOnChanges, you can pass in @Input() values and check
if the component reacts appropriately.
Example:
it('should call ngOnChanges when input changes', () => {
const spyOnChanges =
spyOn(component, 'ngOnChanges');
component.someInput
= 'new value';
component.ngOnChanges();
expect(spyOnChanges).toHaveBeenCalled();
});
20. What is the difference between shallow and deep
testing in Angular components?
Answer:
· Shallow
Testing: In shallow testing, you only test the component itself without
rendering or testing its child components. This isolates the unit of code and
tests it independently.
· Deep
Testing: In deep testing, the component and its entire hierarchy (including
child components) are rendered and tested. This allows you to test how the
component interacts with its children and dependencies.
Example of shallow testing:
TestBed.configureTestingModule({
declarations:
[MyComponent], // Only the component
under test
});
Example of deep testing:
TestBed.configureTestingModule({
declarations:
[MyComponent, ChildComponent], //
Includes child components
});
21. How do you test a component with router dependencies?
Answer:
To test components that depend on the Angular Router, you can use RouterTestingModule
to mock the router functionality. This allows you to simulate navigation, query
parameters, and route activation.
Example:
import { RouterTestingModule } from
'@angular/router/testing';
beforeEach(() => {
TestBed.configureTestingModule({
imports:
[RouterTestingModule], // Mock the
Router module
declarations:
[MyComponent]
});
});
it('should call navigate on button click', () => {
const router =
TestBed.inject(Router);
const spyNavigate =
spyOn(router, 'navigate');
component.someAction();
expect(spyNavigate).toHaveBeenCalledWith(['someRoute']);
});
This tests if the component interacts with the router
correctly.
22. What is the role of inject() in Angular testing?
Answer:
inject() is a utility function provided by Angular's testing module to retrieve
an instance of a service or other injectable class during a unit test. It is
commonly used in combination with TestBed to get access to services for
testing.
Example:
it('should call the fetchData method of MyService',
inject([MyService], (service: MyService) => {
spyOn(service,
'fetchData').and.returnValue(of('mock data'));
component.fetchData();
expect(service.fetchData).toHaveBeenCalled();
}));
inject() is useful when you need to access a service or
dependency directly within a test case, especially if you're not setting up a
full TestBed for the test.
23. How do you test Angular Forms (Reactive Forms or
Template-Driven Forms)?
Answer:
Testing Angular forms involves testing both form controls and form validation.
For Reactive Forms, you can directly access form controls in the
component. For Template-Driven Forms, you can use fixture.detectChanges()
and fixture.debugElement to interact with the form in the template.
Example for Reactive Forms:
it('should mark form as invalid when required fields are
empty', () => {
component.form.controls['name'].setValue('');
component.form.controls['email'].setValue('');
fixture.detectChanges();
expect(component.form.invalid).toBeTrue();
});
Example for Template-Driven Forms:
it('should submit form when valid', () => {
const form =
fixture.debugElement.query(By.css('form')).nativeElement;
form.controls['username'].value = 'user';
form.controls['password'].value = 'password';
form.submit();
expect(component.onSubmit).toHaveBeenCalled();
});
Comments
Post a Comment