Angular Unit Testing inject() without TestBed
Tags: angular, inject, jest, signals
Published: 2023-06-29 17:08:30

This post should serve as a reference.

```ts
// 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)
  }
}
```

```ts
// 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']
    }
  }
}
```

```ts
// 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')
  })
})

```

```ts
// 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')
  })
})
```