This project uses Jasmine and Karma for unit testing Angular components and services.
npm testThis runs all tests once in headless Chrome and exits. Perfect for CI/CD pipelines.
npm run test:watchRuns tests in watch mode with a browser window. Tests re-run automatically when files change.
npm run test:coverageGenerates a code coverage report in the coverage/ directory.
View the report:
open coverage/clinical-trial-portal/index.html# Use Firefox instead of Chrome
npm run test:firefox
# Chrome with CI optimizations
npm run test:chrome
# Full CI test with coverage
npm run test:ciHaving issues in headless environments? See HEADLESS_TESTING.md for detailed setup instructions.
Components:
- ✅
AppComponent- 4 tests- App creation
- Title property
- Header rendering
- Router outlet rendering
Services:
-
✅
TrialService- 4 tests- Service creation
- Fetching trials
- Fetching single trial
- Calculating trial stats
-
✅
ParticipantService- 3 tests- Service creation
- Fetching participants
- Fetching participants by trial
Total: 11 tests passing ✅
import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { MyComponent } from './my.component';
describe('MyComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MyComponent],
providers: [provideRouter([])]
}).compileComponents();
});
it('should create', () => {
const fixture = TestBed.createComponent(MyComponent);
const component = fixture.componentInstance;
expect(component).toBeTruthy();
});
it('should render title', () => {
const fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('My Title');
});
});import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { MyService } from './my.service';
describe('MyService', () => {
let service: MyService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MyService,
provideHttpClient(),
provideHttpClientTesting()
]
});
service = TestBed.inject(MyService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify(); // Verify no outstanding HTTP requests
});
it('should fetch data', () => {
const mockData = [{ id: 1, name: 'Test' }];
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);
});
});it('should do something', () => {
// Arrange - Set up test data and conditions
const fixture = TestBed.createComponent(MyComponent);
const component = fixture.componentInstance;
// Act - Perform the action
component.doSomething();
fixture.detectChanges();
// Assert - Verify the result
expect(component.result).toBe('expected');
});const mockService = jasmine.createSpyObj('MyService', ['getData']);
mockService.getData.and.returnValue(of(mockData));
TestBed.configureTestingModule({
providers: [{ provide: MyService, useValue: mockService }]
});it('should handle async data', fakeAsync(() => {
component.loadData();
tick(); // Simulate passage of time
expect(component.data).toBeDefined();
}));it('should handle button click', () => {
const button = fixture.nativeElement.querySelector('button');
button.click();
fixture.detectChanges();
expect(component.clicked).toBe(true);
});import { provideRouter } from '@angular/router';
TestBed.configureTestingModule({
imports: [MyComponent],
providers: [provideRouter(routes)]
});import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
TestBed.configureTestingModule({
imports: [MyComponent],
providers: [provideAnimationsAsync()]
});it('should validate form', () => {
component.form.setValue({ name: '', email: 'invalid' });
expect(component.form.valid).toBe(false);
component.form.setValue({ name: 'John', email: 'john@example.com' });
expect(component.form.valid).toBe(true);
});it('should emit values', (done) => {
service.data$.subscribe(value => {
expect(value).toBe('test');
done();
});
service.updateData('test');
});ng test --include='**/my.component.spec.ts'ng test --grep='MyComponent'- Run
npm run test:watch - Click "DEBUG" button in Karma browser
- Open browser DevTools
- Set breakpoints in your test code
Issue: Tests timing out
// Increase timeout for specific test
it('should handle slow operation', (done) => {
// test code
}, 10000); // 10 second timeoutIssue: Async test not completing
// Use fakeAsync and tick()
it('should complete async', fakeAsync(() => {
component.asyncMethod();
tick(1000);
expect(component.done).toBe(true);
}));Issue: HTTP requests not mocked
// Always verify no outstanding requests
afterEach(() => {
httpMock.verify();
});- name: Run tests
run: npm test
- name: Generate coverage
run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/clinical-trial-portal/lcov.info- Statements: > 80%
- Branches: > 75%
- Functions: > 80%
- Lines: > 80%
-
Dashboard Component
- Chart data rendering
- Stats calculation
- Loading states
-
Trial List Component
- Table rendering
- Search functionality
- Pagination
- Sorting
-
Trial Detail Component
- Data loading
- Participant list
- Enrollment progress
-
Custom Pipes
- StatusColorPipe transformations
-
Error Handling
- HTTP error responses
- Empty states
- Loading states
When migrating to React, you'll use different testing tools:
| Angular/Jasmine | React |
|---|---|
| Jasmine | Jest |
| Karma | Jest (built-in runner) |
| TestBed | React Testing Library |
| HttpTestingController | MSW (Mock Service Worker) |
| fixture.detectChanges() | render() |
| fixture.nativeElement | screen queries |
import { render, screen, waitFor } from '@testing-library/react';
import { MyComponent } from './MyComponent';
test('renders component', () => {
render(<MyComponent />);
expect(screen.getByText('Hello')).toBeInTheDocument();
});
test('handles click', async () => {
render(<MyComponent />);
const button = screen.getByRole('button');
fireEvent.click(button);
await waitFor(() => {
expect(screen.getByText('Clicked')).toBeInTheDocument();
});
});- Angular Testing Guide
- Jasmine Documentation
- Karma Documentation
- Testing Library (for React migration)
Well-tested code is easier to migrate! 🧪