← Back to list

Angular: Testando Deferrable Views

Nesse post vamos entender como testar um dos mais recentes recursos introduzidos no Angular, as Deferrable Views.

Danilo Lima · 2024-08-24 17:43 · 15 claps · 4.7 min read
#angular #deferrable-views #testing #jest #testing-library
Open on Medium ↗
Wiki topics: 🌐 · Web Development 📚 · Books & Reading

Angular: Testando Deferrable Views

Nesse post vamos entender como testar um dos mais recentes recursos introduzidos no Angular, as Deferrable Views.

Contextualizando

Desde a versão 17 o Angular vem passando por um processo de modernização e renascimento, caracterizado pela introdução de uma série de recursos que buscam melhorar a experiência de desenvolvimento, diminuir a curva de aprendizado e tornar o framework mais performático e completo.

Um desses novos recursos foram as Deferrable Views, que nos permite fazer o lazy loading dos elementos da nossa aplicação a nível de template, por exemplo:

@defer {
  <app-list [posts]="posts()"/>
} @loading {
  <p>Carregando posts...</p>
} @error {
  <p>Erro ao tentar carregar posts...</p>
}

Usamos a declaração @defer no nosso template para indicar que tudo que está no bloco entre a sua chave de abertura e fechamento deve ser carregado de forma tardia, nesse exemplo estamos adiando o carregamento de um componente que renderiza uma lista até que os dados estejam disponíveis para ser renderizado.

Podemos ainda declarar um bloco @loading para renderizar um estado de carregamento e um bloco @error para dar um feedback ao usuário caso houver problema ao carregar o componente de lista.

Testando Deferrable Views

Bem, toda essa inclusão de recursos levanta a discussão sobre testes, e com as Deferrable Views não é diferente, e é disso que vamos aprofundar agora.

O Angular gerencia a renderização dos blocos no contexto das Deferrable Views, ele decide quando cada bloco será renderizado para o usuário, no entanto, num contexto de teste onde queremos reproduzir e validar os comportamentos da nossa View o ideal é que tenhamos mais controle sobre isso.

Felizmente o Angular disponibiliza uma API que nos permite disparar os diferentes estados respectivos a cada um desses blocos, por exemplo:


// Renderiza o estado final
await deferBlockFixture.render(DeferBlockState.Complete);

// Renderiza o estado de loading respectivo ao bloco @loading
await deferBlockFixture.render(DeferBlockState.Loading);

// Renderiza o estado de erro respectivo ao bloco @error
await deferBlockFixture.render(DeferBlockState.Error);

// Não incluímos no nosso exemplo, mas 
// caso tivesse poderiamos renderizar o estado respectivo 
// ao bloco @placeholder
await deferBlockFixture.render(DeferBlockState.Placeholder);

Basicamente, podemos controlar a renderização manualmente e consequentemente controlar o comportamento da nossa View, vamos ver um exemplo completo agora…

Preparando o ambiente

Vamos implementar alguns testes no nosso componente anterior, que serão o seguinte:

  • Testar o cenário feliz, onde a lista de posts foi renderizada;
  • Testar o cenário infeliz, em que houve um erro ao renderizar a lista;
  • Testar o cenário de carregamento, onde o componente de lista ainda está sendo preparado para renderizar;
  • E um plus, testando cenário onde a lista carrega com sucesso, porém está vazia.

Porém, antes de começar, precisamos configurar o ambiente, vamos usar o seguinte biblioteca Angular Testing Library e o Jest, para configurar no seu ambiente use os links abaixo:

Escrevendo os testes

Bora pro primeiro teste, nosso componente responsável por renderizar a lista usa um serviço que requisita dados de uma API, vamos criar um mock para o nosso serviço:

Criando nosso mock

const mockedPosts: Post[] = [
  {
    "id": 1,
    "title": "First Post",
    "body": "This is the first post",
    "tags": [],
    "reactions": {
      "likes": 192,
      "dislikes": 25
    },
    "views": 305,
    "userId": 121
  },
  {
    "id": 2,
    "title": "Second Post",
    "body": "This is the second post",
    "tags": [],
    "reactions": {
      "likes": 192,
      "dislikes": 25
    },  
    "views": 305,
    "userId": 121
  },
]

Configurando o teste

describe('AppComponent', () => {
  const defaultConfig = {
    // Perceba que precisamos mudar o comportamento dos blocos defer 
    // para manual
    deferBlockBehavior: DeferBlockBehavior.Manual,
    providers: [
      {
        provide: PostService,
        useValue: {
          getPosts() {
            return of({
              limit: 10,
              posts: mockedPosts,
              skip: 0,
              total: mockedPosts.length
            } as GetPostsResponse)
          }
        }
      }
    ],
  }
// ...

Testando o cenário feliz — lista renderizada

  it('should display post list', async () => {

    const { fixture } = await render(AppComponent, {
        ...defaultConfig
    });

    // Obtemos referência ao nosso bloco @defer no template
    const deferBlockFixture = (await fixture.getDeferBlocks())[0];

    // Renderizamos o estado final
    await deferBlockFixture.render(DeferBlockState.Complete);    

    // Verificamos se os dois itens da lista estão renderizados
    expect(screen.getByText(mockedPosts[0].title)).toBeInTheDocument();
    expect(screen.getByText(mockedPosts[1].title)).toBeInTheDocument();
  });

Testando cenário de carregamento

  it('show display loading message while posts are still being fetched', async () => {

    const { fixture } = await render(AppComponent, {
        ...defaultConfig
    });

    const deferBlockFixture = (await fixture.getDeferBlocks())[0];

    await deferBlockFixture.render(DeferBlockState.Loading);    

    expect(screen.getByText(/Carregando posts.../i)).toBeInTheDocument();
  });

Testando o cenário infeliz — erro ao carregar posts

it('show display error message when fetching posts returns an error', async () => {

    // Sobrescrevemos o provider para simular
    // um erro vindo do PostService usando a função throwError do RxJS
    const { fixture } = await render(AppComponent, {
        ...defaultConfig,
        providers: [
          {
            provide: PostService,
            useValue: {
              getPosts(){
                return throwError(() => new Error('Error on loading posts'))
              }
            }
          }
        ]
    });

    const deferBlockFixture = (await fixture.getDeferBlocks())[0];

    await deferBlockFixture.render(DeferBlockState.Error);    

    const errorMsgRegex = /Erro ao tentar carregar posts.../i;

    expect(screen.getByText(errorMsgRegex)).toBeInTheDocument();
  });

Testando cenário de lista vazia

O nosso componente de lista utiliza o novo Control Flow do Angular, que foi um dos recursos introduzidos nas novas versões.

Aqui usamos a declaração @for para iterar sobre a lista de posts e renderizar cada um dos itens, o interessante é que podemos definir um bloco @empty, que renderiza um conteúdo quando a lista estiver vazia.

<ul>
  @for (item of posts(); track item) {
    <li data-test="post">
      <h2>{{item.title}}</h2>
      <p>{{item.body}}</p>
      <div>
        <span>Views: {{item.views}}</span>
        <span>Likes {{item.reactions.likes}}</span>
      </div>
    </li>
  } @empty {
    <p>Nenhum post para ser exibido</p>
  }
  </ul>

E para testar, só precisamos renderizar o estado final do bloco @defer:

  it('show display empty state message when fetching posts returns an empty list', async () => {

    const { fixture } = await render(AppComponent, {
        ...defaultConfig,
        // Sobrescrevemos o PostService para retornar uma lista vazia
        providers: [
          {
            provide: PostService,
            useValue: {
              getPosts(){
                return of([])
              }
            }
          }
        ]
    });

    const deferBlockFixture = (await fixture.getDeferBlocks())[0];

    // Renderizando o estado completo do bloco
    await deferBlockFixture.render(DeferBlockState.Complete);    

    const emptyStateMsg = /Nenhum post para ser exibido/i;

    expect(screen.getByText(emptyStateMsg)).toBeInTheDocument();
  });

Resultado dos nossos testes:

Conclusão

As Deferrable Views são um dos melhores recursos incluídos no Angular nos últimos tempos, com ela podemos adiar o carregamento de componentes, diretivas, pipes e qualquer CSS associado a eles.

E adiar o carregamento também significa que o Angular cria um bundle separado para esses elementos. A API é muito poderosa e recomendo dar uma olhada com mais profundidade, podemos ir muito além, só para mencionar, podemos adiar o carregamento de uma View até a iteração do usuário com a UI, por exemplo.

Os testes nesse recurso também traz desafios, mas com uma API simples a experiência de desenvolvimento dos testes se torna mais suave!

E ai, o que achou ?

Se quiser apoiar o meu trabalho reaja a esse post se te ajudei de alguma forma, compartilhe e me siga por aqui ou nas outras redes pra ficar por dentro dos próximos conteúdos.

Até logo, dev! 😉


메타데이터
post_id
1b45461c5e30
slug
angular-testando-deferrable-views-1b45461c5e30
url
https://medium.com/@danlima-dev/angular-testando-deferrable-views-1b45461c5e30
canonical_url
https://medium.com/@danlima-dev/angular-testando-deferrable-views-1b45461c5e30
author_url
https://medium.com/@danlima-dev
status
ok
fetched_at
2026-06-24 04:09:36