11

I have a class that requires the Symfony2 service @request_stack which returns an instance of Symfony\Component\HttpFoundation\RequestStack. I use it to retrieve POST and GET values.

And also my class uses Symfony\Component\HttpFoundation\Session from Request->getSession() which it calls to get the current session.

Right now my class has a method that looks something like this:

class MyClass {
    public function doSomething() {
        //Get request from request stack.
        $Request = $this->RequestStack->getCurrentRequest();

        //Get a variable from request
        $var = $Request->request->get('something');
        //Processes $var into $someprocessedvar and lets say it's equal to 3.
        //Set value to session.
        $this->Request->getSession()->set('somevar', $someprocessedvar);
    }
}

I need to be able to:

  1. Mock RequestStack.
  2. Get Request from RequestStack
  3. Get Session from Request;

With all that said how can I test that MyClass successfully set the expected value in the session?

5 Answers 5

13

Not all code is worth unit testing. Usually this is an indicator that your code could be simplified. When you unit test code that is somewhat complex the tests can become a burden and normally it would be better to do an integration of edge-to-edge test in these cases. It's also not clear in your example how your class gets the RequestStack so I will assume that it has been injected in __construct.

With that said here's how you would test that code:

protected function setUp()
{
    $this->requestStack = $this->getMock('Fully-qualified RequestStack namespace');

    $this->SUT = new MyClass($this->requestStack);
}    

/** @test */
public function it_should_store_value_in_the_session()
{
    $value = 'test value';

    $request = $this->getMock('Request');
    $request->request = $this->getMock('ParameterBag');
    $session = $this->getMock('Session');

    $this->requestStack
        ->expects($this->atLeastOnce())
        ->method('getCurrentRequest')
        ->will($this->returnValue());

    $request->request
        ->expects($this->atLeastOnce())
        ->method('get')
        ->with('something')
        ->will($this->returnValue($value));

    $request
        ->expects($this->once())
        ->method('getSession')
        ->will($this->returnValue($session));

    $session
        ->expects($this->once())
        ->method('set')
        ->with('somevar', $value);

    $this->SUT->doSomething();
}

This should give you a starting point but beware having a wall-of mocks in your tests because very small changes to the implementation details can cause your tests to fail even though the behaviour is still correct and this is something you want to avoid as much as possible so the tests aren't expensive to maintain.

Edit: I thought some more about your question and realized that typically you can inject the Session as a dependency. If that's possible in your use case it would simplify the tests a lot.

Sign up to request clarification or add additional context in comments.

1 Comment

Wrote my own answer if you want to check it out but I accepted your answer because I agree with you for the most part.
8

You don't need to mock RequestStack, it's a super simple class. You can just create a fake request and push it to it. You can also mock the session.

// you can overwrite any value you want through the constructor if you need more control
$fakeRequest = Request::create('/', 'GET');

$fakeRequest->setSession(new Session(new MockArraySessionStorage()));
$requestStack = new RequestStack();
$requestStack->push($fakeRequest);
// then pass the requestStack to your service under test.

But in terms of testing, having to mess around with the internals of a class is not a good sign. Maybe you can create a handler class to encapsulate the logic you need from the request stack so you can test more easily.

Comments

2

It's difficult to imagine a situation where you'd have to be dealing with GET/POST parameters inside a unit-tested class. Have the Controller deal with HTTP requests and sessions (that's pretty much what they're there for), and pass the variables down into the relevant classes to deal with the rest.

That being said, Kevin's response is a possible solution if you want to go down that route.

2 Comments

It's a class to map entities to sessions. It's not just getting data, it's setting it to sessions as well. It's a lot more reusable than having all that logic in the controller.
@tek in that case can't you just mock the session and whatever other dependencies your class needs. The container should never be used in a unit test because how can you mock dependencies resolved out of the container?
1

According to this: http://api.symfony.com/2.4/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.html

I got to work something like the following:

public function testCompanySession()
{
    $Request = new Request();
    $Request->setSession(
        new Session(new MockArraySessionStorage())
    );

    $CompanySessionMapper = new CompanyMapper($Request);

    $Company = new Company();

    $Company->setName('test');

    $CompanySessionMapper->set($Company);

    $Company = new Company();

    $CompanySessionMapper->get($Company);

    $this->assertEquals($Company->getName(), 'test');
}

Only one test per object type in my case since I'm only testing if the session name is correct and retrieving/storing the object properly in the session. CompanyMapper class uses the session to store the company object among other session/application related functions.

Comments

1

Anyone coming from Google like me wants to know how to mock request content, it is as simple as:

use AppBundle\Controller\DefaultController;
use Symfony\Component\HttpFoundation\Request;
use PHPUnit\Framework\TestCase;

class DefaultControllerTest extends TestCase
{
    //@dataProvider?
    public function testWithMockedRequest()
    {
        //create a request mock
        $request = $this
            ->getMockBuilder(Request::class)
            ->getMock();

        //set the return value
        $request
            ->expects($this->once())
            ->method('getContent')
            ->will($this->returnValue('put your request data here'));

        //create your controller
        $controller = new DefaultController();

        //get the Response based on your Request
        $response = $controller->myRequestAction($request);

        //assert!
        $this->assertEquals(200, $response->getStatusCode());
     }
}

As you can see you can execute a real controller which uses $request->getContent()

I hope this helps someone.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.