vincent15000's avatar

Several requests in the same test function

Hello,

I have read in several places that I should never execute several HTTP requests in the same test function.

But ... what if I have exactly the same dataset and if the test is only with GET requests. For example get all tickets, get tickets by user, ...

In this case, is it also rercommended to no execute several GET requests in the same test function ?

Thanks for your answer.

V

0 likes
8 replies
LaryAI's avatar
Level 58

When writing tests, especially in the context of HTTP requests, it's generally recommended to keep each test focused on a single behavior or aspect of your application. This is to ensure that tests are easy to understand, maintain, and debug. However, there are scenarios where making multiple requests in a single test can be justified, particularly if those requests are part of a single logical flow or if they are necessary to set up the state for the test.

Here are some guidelines to consider:

  1. Single Responsibility: Each test should ideally test one thing. If multiple GET requests are necessary to verify a single behavior or outcome, it might be acceptable to include them in one test. However, if each request is testing a different aspect, it's better to separate them into different tests.

  2. Setup and Teardown: If multiple requests are needed to set up the state for a test, consider using setup methods or fixtures to prepare the state before the actual test logic. This keeps the test focused on the behavior you're verifying.

  3. Readability and Maintenance: Ensure that the test remains readable and maintainable. If adding multiple requests makes the test complex or hard to understand, it's a sign that you might need to split it.

  4. Performance Considerations: While making multiple requests in a single test might seem efficient, it can lead to longer test execution times if overused. Consider the performance impact on your test suite.

Here's an example of how you might structure a test with multiple GET requests, ensuring clarity and focus:

public function test_user_can_view_tickets()
{
    // Arrange: Set up necessary data
    $user = User::factory()->create();
    $tickets = Ticket::factory()->count(3)->create(['user_id' => $user->id]);

    // Act: Make the first GET request to retrieve all tickets
    $responseAll = $this->actingAs($user)->get('/tickets');

    // Assert: Check the response for all tickets
    $responseAll->assertStatus(200);
    $responseAll->assertJsonCount(3);

    // Act: Make the second GET request to retrieve tickets by user
    $responseByUser = $this->actingAs($user)->get("/users/{$user->id}/tickets");

    // Assert: Check the response for tickets by user
    $responseByUser->assertStatus(200);
    $responseByUser->assertJsonCount(3);
}

In this example, the test is focused on verifying that a user can view tickets, both generally and filtered by user. The multiple requests are part of a single logical flow, and the test remains clear and focused. If the logic becomes more complex, consider splitting the test into smaller, more focused tests.

martinbean's avatar
Level 80

@vincent15000 The framework isn’t re-booted between requests like it would be in a real application (where each request is handled by a unique process), so it’s possible for state to persist between “requests” and lead to hard-to-find bugs around unexpected state.

1 like
vincent15000's avatar

@martinbean Yes thank you ... I wanted to execute 4 requests in the same test function to test for example the filters for a list of books on a page this :

  • I create 20 books with the factory

  • I see all books on the page

  • I filter by category and I see only the thriller books

  • I filter by publisher and see only the books from this publisher

  • I filter with both a category and a publisher and I see only the books in the category and from the publisher

Each filter is a GET request sent to the database.

Would it be better to write 4 different tests in this specific case ?

Tray2's avatar

@vincent15000 That looks like four feature tests to me.

  • it can list all the books
  • it can filter by category
  • it can filter by publisher
  • it can filter by publisher and category

Here is an example on testing the filter on genre

it('filters on the genre if the query string contains a genre', function () {
    $this->seed(MediaTypeSeeder::class);
    $bookMediaId = MediaType::where('name', 'book')->value('id');
    $genreToSee = Genre::factory()->create(['media_type_id' => $bookMediaId]);
    $genreNotToSee = Genre::factory()->create(['media_type_id' => $bookMediaId]);
    $bookToSee1 = Book::factory()->create(['genre_id' => $genreToSee->id]);
    $bookNotToSee = Book::factory()->create(['genre_id' => $genreNotToSee->id]);

    get(route('books.index', ['genre' => $genreToSee->name]))
        ->assertOk()
        ->assertSeeText([$bookToSee1->title])
        ->assertDontSeeText([$bookNotToSee->title]);
});
1 like
martinbean's avatar

I filter by category and I see only the thriller books I filter by publisher and see only the books from this publisher I filter with both a category and a publisher and I see only the books in the category and from the publisher

@vincent15000 They are three different use cases, so should be three different test cases.

Set up your data, make a request, assert the results. You could use a data provider to re-run a test case, but with different inputs:

public static function filterProvider(): array
{
    return [
        'filter by category' => [
            '?category=computing',
            ['Laravel 5 Essentials', 'Beginning Node.js'],
        ],
        'filter by publisher' => [
            '?publisher=packt',
            ['Laravel 5 Essentials'],
        ],
        'filter by category and publisher' => [
            '?category=computing&publisher=packt',
            ['Laravel 5 Essentials'],
        ],
    ];
}

#[DataProvider('filterProvider')]
public function testFilter(string $filter, array $expectedTitles): void
{
    $response = $this->get('/books' . $filter);

    $response->assertOk();

    foreach ($expectedTitles as $title) {
        $response->assertSeeText($title);
    }
}

This will then run the testFilter test case three times, but passing different arguments in (based on the data provider) each time. If you run phpunit --testdox, you get a nice display of the test cases being run, and the key from the data provider array to show you which variant is being ran.

1 like

Please or to participate in this conversation.