This post should serve as a reference.
// src/app/foo.component.ts
import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
inject,
signal,
} from '@angular/core'
import { User, UserService } from './services/user.service'
@Component({
selector: 'foo-component',
standalone: true,
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<input (input)="input.set($any($event.target).value)" [value]="input()" />
<button (click)="search()">Search</button>
<pre>{{ user() | json }}</pre>
<pre>{{ error() | json }}</pre>
`,
})
export class FooComponent {
userService = inject(UserService)
input = signal('')
user = signal<User | null>(null)
error = signal<string | null>(null)
async search() {
const [user, error] = await this.userService.search(this.input())
this.user.set(user)
this.error.set(error)
}
}
// src/app/services/user.service.ts
import { Injectable } from '@angular/core'
interface Response {
id: number
name: string
email: string
}
export interface User {
id: number
email: string
}
@Injectable({ providedIn: 'root' })
export class UserService {
#url = 'https://jsonplaceholder.typicode.com/users/'
async search(userId: string): Promise<[User, null] | [null, string]> {
try {
const response = await fetch(this.#url + userId)
if (!response.ok) {
throw new Error()
}
const { id, email } = (await response.json()) as Response
const user: User = { id, email }
return [user, null]
} catch (_) {
return [null, 'Failed to fetch user']
}
}
}
// src/app/foo.component.test.ts
import { Injector } from '@angular/core'
import { FooComponent } from './foo.component'
import { UserService } from './services/user.service'
const mockUserService = {
search: jest.fn().mockImplementation((id: string) => {
if (id === '1') {
return Promise.resolve([{ id: 1, email: 'john@example.com' }, null])
} else {
return Promise.resolve([null, 'Failed to fetch user'])
}
}),
}
describe('FooComponent', () => {
const component: FooComponent = Injector.create({
providers: [
{ provide: FooComponent },
{ provide: UserService, useValue: mockUserService },
],
}).get(FooComponent)
it('search(): success', async () => {
component.input.set('1')
await component.search()
expect(component.user()).toEqual({ email: 'john@example.com', id: 1 })
expect(component.error()).toEqual(null)
})
it('search(): error', async () => {
component.input.set('?')
await component.search()
expect(component.user()).toEqual(null)
expect(component.error()).toEqual('Failed to fetch user')
})
})
// src/app/services/user.service.test.ts
import { UserService } from './user.service'
global.fetch = jest.fn(url => {
if (url.endsWith('1')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ id: 1, email: 'test@example.com' }),
})
} else {
return Promise.resolve({ ok: false })
}
}) as jest.Mock
describe('UserService', () => {
const service: UserService = new UserService()
it('search(): success', async () => {
const [user, error] = await service.search('1')
expect(user).toEqual({ id: 1, email: 'test@example.com' })
expect(error).toEqual(null)
})
it('search(): error', async () => {
const [user, error] = await service.search('?')
expect(user).toEqual(null)
expect(error).toEqual('Failed to fetch user')
})
})
Top comments (0)