Laravel PHPUnit mock Request

泄露秘密 提交于 2021-02-19 02:04:12

问题


I'm doing a PHPUnit on my controller and I can't seem to mock the Request right.

Here's the Controller:

use Illuminate\Http\Request;

public function insert(Request $request)
{
    // ... some codes here
    if ($request->has('username')) {
        $userEmail = $request->get('username');
    } else if ($request->has('email')) {
        $userEmail = $request->get('email');
    }
    // ... some codes here
}

Then on the unit test,

public function testIndex()
{
    // ... some codes here

    $requestParams = [
        'username' => 'test',
        'email'    => 'test@test.com'
    ];

    $request = $this->getMockBuilder('Illuminate\Http\Request')
        ->disableOriginalConstructor()
        ->setMethods(['getMethod', 'retrieveItem', 'getRealMethod', 'all', 'getInputSource', 'get', 'has'])
        ->getMock();

    $request->expects($this->any())
        ->method('get')
        ->willReturn($requestParams);

    $request->expects($this->any())
        ->method('has')
        ->willReturn($requestParams);

    $request->expects($this->any())
        ->method('all')
        ->willReturn($requestParams);

    // ... some codes here
}

The problem here is that when ever I var_dump($request->has('username'); it always return the $requestParams value in which is the whole array. I'm expecting that it should return true as the username key exists in the array.

Then when I delete the username key on the $requestParams, it should return false as it does not contain the username key on the array


回答1:


As far as I can see and understand you're telling your unit test that when you call $request->has() on your request object that it should return the $requestParams array, not true or false, or anything else.

Unless you specifically check what is send with a method call your mock doesn't actually care what is send, it just cares that it was called.

You might want to explore creating an empty request and filling it with data if that is possible in your use case as that'll let you run your unit test with more ease and less issues. This won't work in all cases.

You could include what assertions you're making in your unit test so we can see more clearly what you're running into, but as it is. It returns exactly what you're telling it to return. Even if that's not what you actually want it to return.

Mocks are used to separate your Unit-Test from the rest of your system. As such you usually tend to only check if a specific method is called to see if your code actually exits to the class you mocked and if it has the expected data you'd send along. In some extreme cases you can want to mock the system you're actually testing, but this usually indicates that your code is too dependent on other classes or it's doing too much.

Another reason to use mocks is to satisfy Type Casting constraints in your method calls. In these cases you'll usually create an empty mocked object and fill it with some dummy data your code will accept or break on to test the code.

In your case it seems you want to check if your code actually works correctly and for this I'd suggest either not mocking the request, or making specific tests where you tell it to return true, or false (test for both cases)

So something along the lines of:

$request->expects($this->any())
    ->method('has')
    ->with('username')
    ->willReturn(true); // or false in your next test

Edit: As you mentioned in the comment Below you ran into the issue that you're using the has method multiple times in your code and ran into issues.

The Questions I've linked to in my response comment go into greater detail but to sum it up, you can use an inline function or the at() method to deal with multiple cases.

With at() you can supply specific iterations of the code to hit only that bit of the test. It has been mentioned that this makes your tests rather brittle as any has added before the previous ones would break the test.

$request->expects($this->at(0))
    ->method('has')
    ->with('username')
    ->willReturn('returnValue');

$request->expects($this->at(1))
    ->method('has')
    ->with('email')
    ->willReturn('otherReturnValue');

The inline function (callback) solution would allow you to customize your test to allow multiple cases and to return data as required. Unfortunately I'm not too familiar with this concept as I haven't used it myself before. I suggest reading the PHPUnit docs for more information about this.

In the end I'd still suggest not mocking the request and instead making an empty request that you'll fill with the data you want to check. Laravel comes with some impressive methods that'll let you manually fill the request with a lot of data you'd usually test against.

For example you can add data (post/get data) by using

request->add(['fieldname' => 'value'])

As a last few pointers I'd like to mention that it seems you use var_dump. Laravel comes with two of it's own functions that are similar and quite useful in debugging. You can use dd(); or dump(); dd(); dumps and stops the execution of code, while dump(); just outputs whatever you decide. so you could do dd($request); or dump($request); and see what the variables/class objects/etc holds. It'll even put it in a rather spiffy layout with some Javascript and such to allow you to see what's in it and such. Might want to check it out if you didn't knew it existed.




回答2:


Its not ideal to mock Requests, but sometimes you just want to do it anyway:

protected function createRequest(
    $method,
    $content,
    $uri = '/test',
    $server = ['CONTENT_TYPE' => 'application/json'],
    $parameters = [],
    $cookies = [],
    $files = []
) {
    $request = new \Illuminate\Http\Request;
    return $request->createFromBase(
        \Symfony\Component\HttpFoundation\Request::create(
            $uri,
            $method,
            $parameters,
            $cookies,
            $files,
            $server,
            $content
        )
    );
}



回答3:


A simpler answer than @Ian, if your situation is simpler:

Per https://stackoverflow.com/a/61903688/135114, if

  1. your function under test takes a $request argument, and
  2. you don't need to do funky stuff to the Request—real route paths are good enough for you

... then you don't need to "mock" a Request (as in, mockery),
you can just create a Request and pass it, e.g.

public function test_myFunc_condition_expectedResult() {
    ...
    $mockRequest = Request::create('/path/that/I_want', 'GET'); 
    $this->assertTrue($myClass->myFuncThat($mockRequest));
}


来源:https://stackoverflow.com/questions/46358264/laravel-phpunit-mock-request

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!