5

I had a small test done in PHP for a Controller I had written in Symfony2:

class DepositControllerTest extends WebTestCase {

    public function testDepositSucceeds() {

        $this->crawler = self::$client->request(
            'POST',
            '/deposit',
            array( "amount" => 23),
            array(),
            array()
        );

        $this->assertEquals(
            "Deposit Confirmation",
            $this->crawler->filter("title")->text());
    }
}

Up to here, everything was great. Problem started when I realized I wanted to disable possible re-submissions while refreshing the page. So I added a small mechanism to send nonce on every submission.

It works something like this:

class ReplayManager {

    public function getNonce() {
        $uid = $this->getRandomUID();
        $this->session->set("nonce", $uid);
        return $uid;
    }

    public function checkNonce($cnonce) {

        $nonce = $this->session->get("nonce");

        if ($cnonce !== $nonce)
            return false;

        $this->session->set("nonce", null);
        return true;
    }
}

So I had to mofidy the controller to get the nonce when displaying the form, and consume it when submitting.

But now this introduces a problem. I cant make a request to POST /deposit because I dont know what nonce to send. I thought to requesting first GET /deposit to render the form, and setting one, to use it in the POST, but I suspect Symfony2 sessions are not working in PHPUnit.

How could I solve this issue? I would not want to go to Selenium tests, since they are significant slower, not to mention that I would have to rewrite A LOT of tests.

UPDATE: I add a very simplified version of the controller code by request.

class DepositController extends Controller{

    public function formAction(Request $request){

        $this->replayManager = $this->getReplayManager();
        $context["nonce"] = $this->replayManager->getNonce();

        return $this->renderTemplate("form.twig", $context);
    }

    protected function depositAction(){

        $this->replayManager = $this->getReplayManager();
        $nonce               = $_POST["nonce"];

        if (!$this->replayManager->checkNonce($nonce))
            return $this->renderErrorTemplate("Nonce expired!");

        deposit($_POST["amount"]);

        return $this->renderTemplate('confirmation.twig');
    }

    protected function getSession() {
        $session = $this->get('session');
        $session->start();
        return $session;
    }

    protected function getReplayManager() {
        return new ReplayManager($this->getSession());
    }

}
4
  • Could you show your controller code? What else is ReplayManager doing? I think your issue here is Separation of Concerns and your controller code may be useful in confirming that. Commented Nov 7, 2013 at 13:38
  • Done, i wrote a very simplified version of the controller Commented Nov 7, 2013 at 14:52
  • Why do not use PRG pattern? Commented Nov 7, 2013 at 23:52
  • Would PRG protect me if the user pressed F5 before the page loads the instructiong forward()? Commented Nov 8, 2013 at 10:05

2 Answers 2

1

I'm not sure what ReplayManager does, but it looks to me as if it is not the right class to handle the 'nonce'. As the 'nonce' is ultimately stored in and retrieved from the session it should either be handled by the controller or abstracted out into its own class which is then passed in as a dependency. This will allow you to mock the nonce (sounds like a sitcom!) for testing.

In my experience problems in testing are actually problems with code design and should be considered a smell. In this case your problem stems from handling the nonce in the wrong place. A quick refactoring session should solve your testing problems.

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

2 Comments

Agreed - as far as I'm concerned anything requiring the session object should be handled in the controller. However, I don't think this is contributing to the OP's issue in this instance.
Well, the replayManager is handled by the Controller, so isnt it the same? I have updated the controller, to show how I pass the session.
0

It is possible to access the Symfony2 session from PHPUnit via the WebTestCase client. I think something like this should work:

public function testDepositSucceeds() {

    $this->crawler = self::$client->request(
        'GET',
        '/deposit',
    );

    $session = $this->client->getContainer()->get('session');
    $nonce = $session->get('nonce');

    $this->crawler = self::$client->request(
        'POST',
        '/deposit',
        array("amount" => 23, "nonce" => $nonce),
        array(),
        array()
    );

    $this->assertEquals(
        "Deposit Confirmation",
        $this->crawler->filter("title")->text());
}

EDIT:

Alternatively, if there is a problem getting the nonce value from the session, you could try replacing the two lines between the GET and POST requests above with:

$form = $crawler->selectButton('submit');
$nonce = $form->get('nonce')->getValue(); // replace 'nonce' with the actual name of the element

2 Comments

I tried that! The problem is that when I do the POST, the nonce that was created on the first page isn't on the second page. This is what I meant with PHP sessions not working on PHPUnit
Hmmm. I do successfully retrieve a session value within a unit test but I also have a comment against it "// If we get this later, it's empty!" so there was an issue. Having said that, that project is still on Symfony 2.0.4 so things have probably changed - I was hoping for the better but perhaps not?

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.