Question

This is related to this question, but following that solution did not fix my issue. I also realize that Laravel's own documentation states that you should not mock the Request object, but I'm not sure how else to go about writing this test.

Here's a semblance of the code I want to test:

public function getThirdSegment()
{
    return Request::segment(3);
}

Here's a test I currently have:

/**
 * @test
 */
public function indexReturnsOk()
{
    $this->prepareForTests();
    $this->call('GET', '/api/v1/courses');
    $this->assertResponseOk();
    $this->assertResponseStatus(200);
}

This is failing because Request::segment(3) is returning null when running PHPUnit. I first tried to mock the Request object like this:

Request::shouldReceive('segment')->andReturn('courses');

But it still returns null. Then I tried this:

$request = m::mock('Illuminate\Http\Request');
$request->shouldReceive('segment')->andReturn('courses');
Input::swap($request);

And the segment method is still returning null. Is there any way to mock the return value of this method?

Update

This code is within a service provider's register method, but I don't think that's the cause of the issue. Hitting the site from a browser does what I would expect it to do, yet running PHPUnit doesn't seem to flesh out either the route or the URL, or anything having to do with the request itself.

Was it helpful?

Solution

Best answer here so far is I was doing it wrong. Service Providers run way before a controller is even loaded, and, when unit testing, Laravel's Illuminate\Foundation\Testing\TestCase loads the application (calling all service providers' both boot and register methods) during execution of the setUp method, way before any calls can be made out during the execution of any individual test.

I tried finding a solution by moving the logic down and got something to work, something along the lines of:

class MyTestClass extends TestCase
{

    public function setUp()
    {
        // No call to parent::setUp()

        // From: Illuminate\Foundation\Testing\TestCase
        $this->app = $this->createApplication();
        $this->client = $this->createClient();
        // Not this one!
        //$this->app->setRequestForConsoleEnvironment();
        $this->app->boot();

        // ...
    }

    public function testWhatever()
    {
        // Calls to this will now actually have a request object
        $this->call('GET', '/api/v1/courses');
    }
}

But that just can't be right, at least it doesn't feel so.

Instead, I figure it's probably best not to rely on anything in the Request object from within Service Providers. Instead, why not just inject an object that can do what I need it to do in the controller I want it to? No Service Provider necessary, and I can easily mock any object other than Request in Unit Tests. I should've believed the docs.

Update

Taking this answer to my own question a bit further, I believe my original mistake was that I was utilizing the Request object within a Service Provider. I think, on retrospection, that you should probably never use the Request object at all within a service provider, because providers get loaded for everything related to laravel (including artisan commands, which of course should have no request). My original code worked in the browser, but I probably would have noticed issues had I tried to run any artisan commands.

OTHER TIPS

I came across this looking for a good way to handle a view composer that uses the request, and ultimately ended up going with constructor injection - because Laravel uses the Symfony Request class, it was really easy to "mock" a request with Request::create('http://myurl.com'), and pass it into an instance of my class for testing. Much better than trying to mess with stubbing methods on the request class, and doesn't rely on building up the whole Laravel application.

For clarity's sake, the view composer looks generally like this:

class SiteLayoutComposer extends BaseComposer {

    function __construct(Request $request) {
        $this->request = $request;
    }

    public function compose($view)
    {
        $templateName = preg_match('/siteurlexample/', $this->request->getHost()) ? 'site1' : 'site2';
        $view->with('siteLayout', "layouts.sites.{$templateName}");
    }

}

And the test:

class SiteLayoutComposerTest extends TestCase {

    public function testWillSetTheSiteLayoutToSite1()
    {
        $request = Request::create('http://www.siteurlexample.com');
        $view = View::make('home');
        $composer = new SiteLayoutComposer($request);
        $composer->compose($view);
        $this->assertEquals('layouts.sites.site1', $view->siteLayout);
    }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top