Summer Sale! All accounts are 50% off this week.

bor1904's avatar

Testing requests and/or store/update methods in controller

Hi Guys, I read in few places that a good practice is testing controller methods as endpoints, setters getters in models, services, helpers.... but in many places I saw very smart way for testing Requests (forms validation) ... So if i test requests used in update and store method in controller then how/if should I test these methods in controller too ?

Or maybe Im doing something wrong ?

0 likes
7 replies
bor1904's avatar

"So if I test App\Http\Requests used in update and store method in controller then how/if should I test these methods in controller too ?"

martinbean's avatar

@bor1904 If you’re asking if you should test form requests separately from controllers then no, personally I don’t do that. I just create test cases in my feature tests that deliberately send back data in a request and I assert I get the expected validation errors.

bor1904's avatar

but even always in controller method exists some additional "functionalities" ... model crreation, firing event, select view, pass data view and so on ... and I understand you dont test any way this mechanisms ?

I finally test controller methods checking Request rules and comming back errors + finally i check in db if model was created ... this giving me a bit more info

martinbean's avatar

@bor1904 A feature test is a high-level test. You can easily test a controller creates/updates the relevant models, fires events, etc. But you would have separate test cases that test these things individual, instead of one, massive test that tries to test everything. This is why they’re called test cases. You have a test case per business use case.

public function testGuestCannotCreateArticle(): void
{
    $this
        ->post('/articles', [
            'title' => 'Test Article',
            'summary' => 'This is a test article.',
            'body' => 'This is a test article. Enjoy reading!',
        ])
        ->assertRedirect('/login');
}

public function testAuthenticatedUserCanCreateArticle(): void
{
    $user = User::factory()->create();

    $this
        ->actingAs($user)
        ->post('/articles', [
            'title' => 'Test Article',
            'summary' => 'This is a test article.',
            'body' => 'This is a test article. Enjoy reading!',
        ])
        ->assertSessionHasNoErrors()
        ->assertRedirect('/')
        ->assertSessionHas('success');

    $this->assertDatabaseHas('articles', [
        'title' => 'Test Article',
        'summary' => 'This is a test article.',
        'body' => 'This is a test article. Enjoy reading!',
    ]);
}

/**
 * @dataProvider requiredFieldsDataProvider
 */
public function testFieldIsRequired(string $field): void
{
    $user = User::factory()->create();

    $data = [
        'title' => 'Test Article',
        'summary' => 'This is a test article.',
        'body' => 'This is a test article. Enjoy reading!',
    ];

    Arr::forget($data, $field);
    
    $this
        ->actingAs($user)
        ->post('/articles', $data)
        ->assertSessionHasNoErrors()
        ->assertRedirect('/')
        ->assertSessionHas('success');

    $this->assertDatabaseHas('articles', [
        'title' => 'Test Article',
        'summary' => 'This is a test article.',
        'body' => 'This is a test article. Enjoy reading!',
    ]);
}

public function requiredFieldsDataProvider(): array
{
    return [
        'title' => ['title'],
        'summary' => ['summary'],
        'body' => ['body'],
    ];
}

public function testEventIsDispatched(): void
{
    Event::fake();

    $user = User::factory()->create();

    $this
        ->actingAs($user)
        ->post('/articles', [
            'title' => 'Test Article',
            'summary' => 'This is a test article.',
            'body' => 'This is a test article. Enjoy reading!',
        ])
        ->assertSessionHasNoErrors()
        ->assertRedirect('/')
        ->assertSessionHas('success');

    Event::assertDispatched(ArticleCreated::class);
}

If you ran that test class with the --testdox option, you’ll get each test case’s name printed in a human-readable format, and if any test fails then you’ll immediately see which case failed (i.e. the testEventIsDispatched case might fail if you weren’t dispatching that event in the controller) meaning you can quickly pinpoint the errors in the application, rather than just having one single canCreateArticle test, as if that failed you would have a lot of logic to go through to see why it failed.

1 like
bor1904's avatar

@martinbean thankk you for detailed explanation but I still have some doubts.

To be sure, do you suggest to make a lot of unit tests + feature tests in amunt equeals to use cases/business requirements* in spec?

( if yes -> additional doubt: -from my expirience we never have described all business needs and constraints in spec ... for example now I wrote middle size system with over 130 Requests, most of them has over 5 fields and avg one field has about 4 rules ... this is only part of only constraints described this system. To describe all business needs in details in that kind of system I assume we need about 1500 pages spec. When you finish reading this "book" you forgot what was on first page. second thing ... "one sentence" from this book = one functional test that is 100MB of test code in repo :D)

Question about simple example - controller method :


public function store(DedicstedRequest $req){

	SomeModel::addNewx($req)

	return ...
}

I checked Request (rules), checked if proper request was used in method, checked model method addNew separately....

but in controller I had a typo .. "addNewx".

All test were green but storing method doesnt work. But if I wrote a test for this then I duplicate many of test area.

@martinbean Can you add your comment related to above ? thank you

martinbean's avatar

To be sure, do you suggest to make a lot of unit tests + feature tests in amunt equeals to use cases/business requirements* in spec?

@bor1904 I have very few unit tests in my applications. I mainly have feature tests where I’m testing my application how it will be used: via HTTP requests coming in and asserting the business logic I expect takes place (models created, events dispatched, queue jobs pushed, etc).

Question about simple example - controller method :

I checked Request (rules), checked if proper request was used in method, checked model method addNew separately....

but in controller I had a typo .. "addNewx".

All test were green but storing method doesnt work. But if I wrote a test for this then I duplicate many of test area.

@bor1904 Then you weren’t actually testing the controller action and what it was doing. If you had a test that asserted a model was inserted into the database after the controller action had run, then this would have failed when the typo was introduced.

Please or to participate in this conversation.