0

I am writing test for some controller method that will validate request data and create document by 3rd party API. Then it should return response with 201 status code. I am using mocking to mock my service class that is creating document. Here is my controller:

public function margin(MarginRequest $request){
    $data = $request->validated();

    $fileId = $this->documentService->createDocument(auth()->user()->id, SignableDocumentAbstract::MARGIN_CERTIFICATE, new MarginDocument(), $data);

    return response()->json([
        'code' => 201,
        'message' => 'Margin agreement generated successfully',
        'data' => [
            'uuid' => $fileId
        ]
    ], 201);
}

And my test:

public function test_public_margin()
{
    $marginData = (new MarginFaker())->fake();

    Auth::shouldReceive('user')->once()->andReturn((object)['id' => 999999]);

    $this->mock(DocumentService::class, function (MockInterface $mock) {
        $mock->shouldReceive('createDocument')
            ->once()
            ->andReturn(Str::random());
    });

    $request = MarginRequest::create('/api/public/documents/margin', 'POST', $marginData);

    $response = app(PublicController::class)->margin($request);

    $this->assertEquals(201, $response->getStatusCode());
}

Everything look OK but when I run my test it throws error that

Call to a member function validated() on null

It is given in $data = $request->validated(); line of controller. But I can't understand why my $request is recognized as null. Even if I dump request object by dump($request) I can see that it is object and holds all required fields inside.

Then what can be the reason, why I can't call validated() method while testing?

Vüsal Hüseynli
  • 889
  • 1
  • 4
  • 16
  • Is there a reason why you can't make the response = "$this->post("{your url}", {your request data})", and then have the assertion be "$response->assertStatus(201)"? – Daniel Haven Jan 16 '23 at 13:46
  • @DanielHaven Can I make API request while using mock? Is it gonna fake the method I have defined or not? – Vüsal Hüseynli Jan 16 '23 at 14:00
  • 1
    API responses are meant to be mocked. If you're testing a method that can be accessed via HTTP, try that. – Daniel Haven Jan 16 '23 at 17:17

1 Answers1

1

You do not test like that when you want to test a URL. You NEVER mock a controller or do new Controller and call a method inside it.

You have to read the HTTP Test section of the documentation.

So, your test should look like this:

public function test_public_margin()
{
    $this->actingAs(User::factory()->create());

    $this->mock(DocumentService::class, function (MockInterface $mock) {
        $mock->shouldReceive('createDocument')
            ->once()
            ->andReturn(Str::uuid());
    });

    $response = $this->post(
        '/api/public/documents/margin', 
        ['pass the needed data as an array, so the validation passes']
    );

    $response->assertStatus(201);
}
matiaslauriti
  • 7,065
  • 4
  • 31
  • 43
  • Thanks for answer. I thought there is not that much difference between testing controller method by creating object or accessing it with API request. But I understood that if I call controller method directly, request class it not even fired to validate data. Thats why I will need to change my approach to API testing for controllers. – Vüsal Hüseynli Jan 17 '23 at 04:12
  • @VüsalHüseynli yes, but not only that, you are also not testing middlewares, they are important too! – matiaslauriti Jan 17 '23 at 04:14
  • @matiaslauriti If you're trying to test the controller itself, it's better to disable the middleware and test it in its own test. https://stackoverflow.com/questions/34357350/how-to-disable-selected-middleware-in-laravel-tests – Daniel Haven Jan 17 '23 at 12:55
  • @DanielHaven I never had the need to do so, and I have been working with Laravel for 5+ years now. A feature test usually tests everything... unless you have a lot of middlewares, I would test the URL itself, because you must make sure you have what you need to have on that route, including middlewares... Check my profile (I have some SO answers on my bio) related to testing – matiaslauriti Jan 17 '23 at 19:22
  • @matiaslauriti My apologies. – Daniel Haven Jan 18 '23 at 14:53