React Testing Library: Jak sprawdzić kolejność wyświetlanych elementów na liście?

Opublikowano: 17.10.2023 - tagi: JavaScript React Testowanie Test Lista

Jaka kolejność na liście?

Można to sprawdzić na kilka sposobów.

Najpierw kod przykładowej aplikacji:

import { useState } from "react";

export function MyComponent() {
    const [tasks, setTasks] = useState(['Task 1', 'Task 2', 'Task 3']);
    const [task, setTask] = useState('');

    const onChangeTask = (e) => {
        setTask(e.target.value)
    };

    const onAddTask = (e) => {
        e.preventDefault();

        setTasks([task, ...tasks]);
        setTask('');
    }

    return (
        <>
            <form onSubmit={onAddTask}>
                <label htmlFor="newTask">Task title:</label>
                <input type="text" id="newTask" value={task} onChange={onChangeTask} />
                <button>Add</button>
            </form>
            <ul>
                {
                    tasks.map((task, index) => (
                        <li key={index}>
                            {task}
                        </li>
                    ))
                }
            </ul>
        </>
    )
}

Przykłady

Sposób 1: toMatchInlineSnapshot

Jest udostępnia funkcję toMatchInlineSnapshot testowania struktury DOM.

import { render, screen } from '@testing-library/react';
import { MyComponent} from "./MyComponent";

test('should check order of list items', async () => {
    // given
    const { getByRole } = screen;
    render(<MyComponent />);

    // when
    const list = getByRole('list');

    // then
    expect(list).toMatchInlineSnapshot(`
        <ul>
          <li>
            Task 1
          </li>
          <li>
            Task 2
          </li>
          <li>
            Task 3
          </li>
        </ul>
    `)
});

Jeśli element listy jest dość rozbudowany takie testowanie może być uciążliwe. Ale można to obejść w prosty sposób!

Wywołaj najpierw test z toMatchInlineSnapshot bez żadnych argumentów:

expect(list).toMatchInlineSnapshot();

po chwili zostanie dodany argument do toMatchInlineSnapshot z listą elementów.

Sposób 2: Tablica elementów

Możesz też pobrać nazwy elementów i umieścić je w tablicy:

import { render, screen } from '@testing-library/react';
import { MyComponent} from "./MyComponent";

test('should check order of list items', async () => {
    // given
    const { getAllByRole } = screen;
    render(<MyComponent />);

    // when
    const listItems = getAllByRole('listitem').map((item) => item.textContent);

    // then
    expect(listItems).toEqual(['Task 1', 'Task 2', 'Task 3']);
});

React Testing Library: Within

Opublikowano: 14.10.2023 - tagi: JavaScript React Testowanie Test Lista

Do czego służy within?

React Testing Library udostępnia funkcję o nazwie within, która pozwala na pisanie zapytań tylko w obrębie danego fragmentu z DOM.

Załóżmy, że mamy listę i każdy element listy składa się z innych elementów. Na przykład:

<ul>
	<li>
		<h2>Item 1</h2>
		<button type="button">Edit</button>
		<button type="button">Remove</button>
	</li>
	<li>
		<h2>Item 2</h2>
		<button type="button">Edit</button>
		<button type="button">Remove</button>
	</li>
</ul>

Teraz chcemy napisać test, który sprawdzi usuwanie elementu z listy. Jak pobrać referencję do przycisku: "Remove"? Można każdemu przycisku nadać unikalne id za pomocą data-testid.

Można też użyć funkcji within wystarczy, że przekażemy do niej pojedynczy element listy. Następnie za pomocą zapytania zdobyć referencję do przycisku.

Przykład

Poniżej znajduje się prosty kod TODO listy.

Komponent:

import {useState} from "react";

export function MyComponent() {
    const [tasks, setTasks] = useState(['Task 1', 'Task 2', 'Task 3']);
    const [task, setTask] = useState('');

    const onChangeTask = (e) => {
        setTask(e.target.value)
    };

    const onAddTask = (e) => {
        e.preventDefault();

        setTasks([task, ...tasks]);
        setTask('');
    }

    const onRemoveTask = (taskToRemove) => {
        setTasks(tasks.filter(currentTask => currentTask != taskToRemove))
    }

    return (
        <>
            <form onSubmit={onAddTask}>
                <label htmlFor="newTask">Task title:</label>
                <input type="text" id="newTask" value={task} onChange={onChangeTask} />
                <button>Add</button>
            </form>
            <ul>
                {
                    tasks.map((task, index) => (
                        <li key={index}>
                            <span data-testid="name">{task}</span>
                            <button type="button" onClick={() => onRemoveTask(task)}>Remove</button>
                        </li>
                    ))
                }
            </ul>
        </>
    )
}

I chcesz stestować usuwanie zadania (element listy).

Test:

import {render, screen, within} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {MyComponent} from "./MyComponent";

test('should remove task from list', async () => {
    // given
    const { getAllByRole, queryByText } = screen;
    render(<MyComponent />);
    const listItems = getAllByRole('listitem').map((item) => ({
        name: within(item).getByTestId('name').textContent,
        removeBtn: within(item).getByRole('button', { name: 'Remove' }),
    }));
    const removeTaskBtn = listItems.find((item) => item.name === 'Task 1').removeBtn;

    // when
    await userEvent.click(removeTaskBtn);

    // then
    expect(queryByText('Task 1')).not.toBeInTheDocument();
});

Najpierw iterujemy po wszystkich elementach listy za pomocą getAllByRole. Tworzona jest tablica, która zawiera nazwę elementu i referencję do przycisku do usuwania zadania.

Przy każdej iteracji korzystamy z funkcji within. Przekazywany jest do niej pojedynczy element listy. Dzięki temu możesz napisać zapytanie: Pobierz mi przycisk o nazwie "Remove". Nie ważne, że na liście znajduje się więcej niż jeden taki przycisk o tej samej nazwie!


JavaScript: Jak pobrać n pierwszych elementów tablicy?

Opublikowano: 10.10.2023 - tagi: JavaScript Tablica Element

N pierwszych elementów

Mamy tablicę:

const items = [1, 2, 3, 4, 5];

I potrzebujesz pobrać trzy pierwsze elementy tablicy.

Jak można to zrobić?

Sposób 1: slice

const itemsB = items.slice(0, 3);
console.log(itemsB); // [1, 2, 3]

Sposób 2: length

items.length = 3;
console.log(items); // [1, 2, 3]

Swift: Computed property

Opublikowano: 07.10.2023 - tagi: Swfit Atrybut Zmienna

Czym jest computed property?

Zmienna, która jest computed property działa na takiej zasadzie, że nie przechowuje wartości, tylko jest ona obliczana przy próbie odczytu.

Domyślnie udostępnia getter do pobierania wartości. Opcjonalnie można przypisać także setter jeśli chcemy przypisywać zmiennej wartość.

Computed property można użyć w klasie, strukturze, enum lub nawet poza wymienionymi.

Najprościej można stworzyć computed property, w taki sposób:

struct User {
	var firstName: String;
	var lastName: String;
	var fullName: String {
		return firstName + " " + lastName
	}
}

var user = User(firstName: "James", lastName: "Bond");
print(user.fullName) // James Bond

Zmienna fullName jest typu: computed property. W momencie odwołania się do niej w kodzie jej wartość jest ustalana "w locie".

Zapis dla fullName można jeszcze skrócić:

var fullName: String {
	firstName + " " + lastName
}

O ile w Swift nie jest wymagane określanie typu danych zmiennej lub stałej, to w przypadku computed property taki typ musi zostać podany.

Getter

Pobieranie wartości dla computed property można zdefiniować jeszcze w inny sposób:

var fullName: String {
	get {
		firstName + " " + lastName
	}
}

Tylko po co tak pisać skoro można obyć się bez get?

Istnieje możliwość dodania setter'a dla computed property.

Setter

Jeśli chcesz zmienić wartość computed property musisz określić mu setter. Bez tego próba przypisania wartości spowoduje błąd.

struct Hotel {
  var rooms: Int
	var allocated: Int = 0
	var roomsRemaining: Int {
		get {
			rooms - allocated
		}
		set {
			rooms = allocated + newValue
		}
	}
}

var hotel = Hotel(rooms: 20)
print(hotel.roomsRemaining) // 20
hotel.allocated = 5
print(hotel.roomsRemaining) // 15
hotel.roomsRemaining = 10 
print(hotel.rooms) // 15
print(hotel.roomsRemaining) // 10

Kiedy tego użyć?

Jeśli tworzysz zmienną, która zależy od wartości innej zmiennej, jest to sygnał, że warto zadeklarować ją jako computed property.

Z drugiej strony należy uważać, bo możemy przesadzić. Wartość computed property jest obliczana za każdym razem, gdy pobierana jest z niej wartość. Więc jeśli umieścisz w takiej zmiennej kosztowne operacje, zapłacisz spadkiem wydajności aplikacji.


React Testing Library: Jak mockować requesty?

Opublikowano: 03.10.2023 - tagi: JavaScript React Testowanie Test Mock Request

Testowanie requestów

Pisanie testów dla funkcjonalności, które wykorzystują requesty może być irytujące. W zależności, jak taki test napiszesz.

Możesz mockować funkcje biblioteki, którą używasz do obsługi reqestów.

Albo przyjąć inne podejście: mockujesz tylko same requesty. To znaczy: jeśli w kodzie zostanie wywołany GET /api/user, ustalasz, że ma zwrócić konkretne dane i tyle!

Na pierwszy rzut oka oba podejścia mogą wydawać się podobne, ale tak nie jest.

W pierwszym podejściu skupiasz się niepotrzebnie na szczegółach: mockujesz konkretną bibliotekę i metodę. Co jeśli w przyszłości będziesz chciał zmienić bibliotekę do obsługi requestów? Po zmianie testy oczywiście się załamią.

Znacznie lepszym rozwiązaniem jest mockowanie samych requestów. Dzięki temu skupiasz się na testowaniu tego, co w rzeczywistości jest ważne: na testowaniu funkcjonalności, a nie szczegółach implementacji

Więc jak mockować requesty?

Jest kilka gotowych bibliotek. W tym wpisie opiszę, jak działa Nock.

Nock

Jest to prosta biblioteka, która pozwala w banalny sposób mockować requesty.

Instalacja

Żeby zainstalować nock'a wywołaj komendę:

npm i nock -d

Jak korzystać z Nock?

Poniżej przykład mockowania request'a typu GET:

import nock from 'nock';

nock(host)
	.get(endpoint)
	.reply(200, response);

Gdzie odpowiednio:

  1. host — To adres do naszego API.
  2. endpoint — To, co chcemy zamokować.
  3. response — Możemy też określi, co zwróci endpoint po jego wywołaniu.

Przykładowa aplikacja

Do stestowania jest: wczytywanie listy zadań oraz zapis nowego zadania.

Do obsługi requestów w tym przykładzie korzystam z biblioteki axios.

Komponent:

import {useState, useEffect} from "react";
import axios from "axios";

interface Task {
    id: number;
    title: string;
};

export function MyComponent() {
    const [tasks, setTasks] = useState<Task[]>([]);
    const [task, setTask] = useState<string>('');

    useEffect(() => {
        axios.get('/api/task').then(res => setTasks(res.data));
    }, []);

    const onAddTask = () => {
        axios.post('/api/task', { task }).then(res => {
            const { id } = res.data;
            setTask('');
            setTasks([{ id, title: task }, ...tasks]);
        })
    }
    return (
        <>
            <div>
                <label htmlFor="task">Task title:</label>
                <input name="task" id="task" value={task} onChange={(e) => setTask(e.target.value)} />
                <button onClick={onAddTask}>Add</button>
            </div>
            <ul>
                {
                    tasks.map((task: Task) => <li key={task.id}>{task.title}</li>)
                }
            </ul>
        </>
    )
}

Testowanie GET: wczytywanie listy

Test:

import {render, screen} from '@testing-library/react';
import nock from 'nock';
import {MyComponent} from "./MyComponent";

test('should display list of tasks', async () => {
    // given
    nock('http://localhost')
        .get('/api/task')
        .reply(200, [
            {
                id: 1,
                title: "Task 1"
            },
            {
                id: 2,
                title: "Task 2"
            },
            {
                id: 3,
                title: "Task 3"
            }
        ]);

    // when
    render(<MyComponent />)

    // then
    expect(
        await findByText('Task 1')
    ).toBeInTheDocument();

    expect(
        await findByText('Task 2')
    ).toBeInTheDocument();

    expect(
        await findByText('Task 3')
    ).toBeInTheDocument();
});

Testowanie POST: zapis danych

Test:

import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import nock from 'nock';
import {MyComponent} from "./MyComponent";

test('should add new task', async () => {
    // given
    nock('http://localhost')
        .get('/api/task')
        .reply(200, [
            {
                id: 1,
                title: "Task 1"
            },
            {
                id: 2,
                title: "Task 2"
            },
            {
                id: 3,
                title: "Task 3"
            }
        ])
        .post('/api/task')
        .reply(200, {
            id: 4
        });
    render(<MyComponent />);
    const taskInput = getByLabelText('Task title:');
    await userEvent.type(taskInput, 'Task 4');
    const taskAddBtn = getByRole('button', { name: 'Add' });

    // when
    await userEvent.click(taskAddBtn);

    // then
    expect(
        await findByText('Task 4')
    ).toBeInTheDocument();
});