How to test form request rules in Laravel 5? - forms

I created a form request class and defined a bunch of rules. Now I would like to test these rules to see if the behaviour meets our expectations.
How could I write a test to accomplish that?
Many thanks in advance for your answers!
Update: more precisely, I would like to write a unit test that would check e.g. if a badly formatted email passes validation or not. The problem is that I don't know how to create a new instance of the Request with fake input in it.

The accepted answer tests both authorization and validation simultaneously. If you want to test these function separately then you can do this:
test rules():
$attributes = ['aa' => 'asd'];
$request = new MyRequest();
$rules = $request->rules();
$validator = Validator::make($attributes, $rules);
$fails = $validator->fails();
$this->assertEquals(false, $fails);
test authorize():
$user = factory(User::class)->create();
$this->actingAs($user);
$request = new MyRequest();
$request->setContainer($this->app);
$attributes = ['aa' => 'asd'];
$request->initialize([], $attributes);
$this->app->instance('request', $request);
$authorized = $request->authorize();
$this->assertEquals(true, $authorized);
You should create some helper methods in base class to keep the tests DRY.

You need to have your form request class in the controller function, for example
public function store(MyRequest $request)
Now create HTML form and try to fill it with different values. If validation fails then you will get messages in session, if it succeeds then you get into the controller function.
When Unit testing then call the url and add the values for testing as array. Laravel doc says it can be done as
$response = $this->call($method, $uri, $parameters, $cookies, $files, $server, $content);

Here's a full example of testing validation:
use App\Http\Requests\PostRequest;
use Illuminate\Routing\Redirector;
use Illuminate\Validation\ValidationException;
class PostRequestTest extends TestCase
{
protected function newTestRequest($data = [])
{
$request = new PostRequest();
$request->initialize($data);
return $request
->setContainer($this->app)
->setRedirector($this->app->make(Redirector::class));
}
public function testValidationFailsWhenEmptyTitleIsGiven()
{
$this->expectException(ValidationException::class);
$this->newTestRequest(['title' => ''])->validateWhenResolved();
}
}

Related

Shopware 6 : How to get dynamic url in custom plugin controller?

Shopware 6
I am new to shopware. I want base url in controller ( custom plugin ).
I tried this but it's not working for me.
$salesChannel->getDomains()->first()->getUrl();
Thanks in advance
If you just need the current host and base url in your controller:
use Shopware\Storefront\Framework\Routing\RequestTransformer;
use Symfony\Component\HttpFoundation\Request;
// ...
/**
* #Route(...)
*/
public function myAction(Request $request): Response
{
$host = $request->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL)
. $request->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL);
// ...
}
First : Add this namespace
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
Second : here you can get base url.
$urls = [];
$salesChannelRepository = $this->container->get('sales_channel.repository');
$criteria = new Criteria();
$criteria->addAssociation('domains');
$salesChannelIds = $salesChannelRepository->search($criteria, Context::createDefaultContext());
foreach($salesChannelIds->getEntities()->getElements() as $key => $salesChannel){
foreach($salesChannel->getDomains()->getElements() as $element){
array_push($urls, $element->getUrl());
}
}
you will get your url in $urls variable.
If you have access to the request object which you should have in a controller, you have access to an request attribute called sw-storefront-url. This will give you exactly what you need: the base-url of the current request.
See also How to get the current saleschannel domain in Shopware 6 storefront/TWIG?

Silverstripe prevent load data when submitting a form

I need a clean solution to set data after submit a page from being populated by :
$form->loadDataFrom( $Page );
There is my code :
public function FormUpdate() {
$error="Required";
$fields = new FieldList(
TextField::create('Title', 'Title')->setCustomValidationMessage($error),
TextField::create('Description', 'Description')->setCustomValidationMessage($error),
TextField::create('Subject', 'Description')->setCustomValidationMessage($error),
);
$actions = new FieldList(
FormAction::create("FormUpdateSubmit")->setTitle('Update')
);
$Page=Versioned::get_by_stage('Page', 'Live')->filter( array('SecureCode' => $_REQUEST['id'] ))->First();
$fields->push( HiddenField::create('id','SecureCode', $Page->SecureCode ));
$fields->push( CheckboxField::create('Approbation', "Approbation")->setCustomValidationMessage($error) ); ),
$required = new RequiredFields(array(
'Title','Subject','Description'
));
$form = new Form($this, 'FormModifier', $fields, $actions, $required);
$form->loadDataFrom( $Page );
$form->setAttribute('novalidate', 'novalidate');
return $form;
}
The problem... If I change Title and Description and I empty Subject field, i'm redirected back to the form page with the error message below Subject but, All fields are reloaded from $form->loadDataFrom($Page); That wasn't good. I must prevent that data to be reloaded. In this case, datas posted must replace $Page. What I have missing?
I generally use loadDataFrom on the action that called the form (rather than inside the form function). So for example:
...
public function index()
{
$form =$this->Form();
$form->loadDataFrom($this);
$this->customise(array("Form" => $form));
return $this->renderWith("Page");
}
...
That way the function only returns the base form and you alter it as and when required.
Your form will be called once when adding it in the template, and once via request. Since all actions on a controller get the request as parameter, you can modify your form function like so:
public function FormUpdate($request = null) {
Then inside your function, only populate the form if it's not called via a request, eg.
if (!$request) {
$form->loadDataFrom($Page);
}

Dingo Api response->created | location and content example

I am creating API with Laravel 5.2 and Dingo API package. When a user is created, I want to return 201 response with the new $user->id.
My Code
return $this->response->created();
As per Dingo documentatio, I can provide a location and $content as parameters in the created() function.
My question is, what location information I need to return here and I tried to set my new user as $content, but it's not working or I am not sure what to expect.
Can someone please explain this created() function?
What this does is set the Location header, as seen in the source:
/**
* Respond with a created response and associate a location if provided.
*
* #param null|string $location
*
* #return \Dingo\Api\Http\Response
*/
public function created($location = null, $content = null)
{
$response = new Response($content);
$response->setStatusCode(201);
if (! is_null($location)) {
$response->header('Location', $location);
}
return $response;
}
So, in your example since you're creating a new user, you might send the users profile page as the location, something like:
return $this->response->created('/users/123');
As for the content, as you can see in the function this sets the content on the return. In your case, it would probably be a json string with the new user information, something like:
return $this->response->created('/users/123', $user); // laravel should automatically json_encode the user object

Using Zend2 Flashmessenger Controller/Plugin to show success msg

I am using FlashMessenger Controller/Plugin to show error messages, and that works fine. I am trying to show success messages by setting the namespace to success.
$this->flashMessenger()->addSuccessMessage('Success msg!');
$successMessages = $this->flashMessenger()->hasSuccessMessages();
print_r($successMessages);exit;
$view = new ViewModel(array('success' => $successMessages));
When i print $successMessages it shows Array() as there are no messages to show.
Same is if i try to set namespace like this.
$this->flashMessenger()->setNamespace('success')->addSuccessMessage('Success msg!');
$successMessages = $this->flashMessenger()->setNamespace('success')->getMessages();
Is this the propper way to set namespace and how to show the message on view?
I am using this to show error messages, but it doesn't work with success messages.
<?php if (!empty($this->messages)): ?>
<?php foreach ($this->messages as $msg) : ?>
<div class="error-box"><?php echo $this->translate($msg) ?></div>
<?php endforeach; ?>
<?php endif; ?>
This is my FlashMessenger class.
It looks like you are writing and reading the messages in the same request.
Think that flashMessenger is mainly meant to save messages for later, as you can read in the old but meaningful ZF1 docs:
The FlashMessenger helper allows you to pass messages that the user
may need to see on the next request.
For example, you are in a request that comes from a form that is meant to add records to a list, in the controller/action that receives the form you save a record, write the message to the flashmessenger, and redirect the user to the controller/action that show the list of records again.
Something that you can see in this example from the ZF2 mvc plugins docs:
public function processAction()
{
// ... do some work ...
$this->flashMessenger()->addMessage('You are now logged in.');
return $this->redirect()->toRoute('user-success');
}
public function successAction()
{
$return = array('success' => true);
$flashMessenger = $this->flashMessenger();
if ($flashMessenger->hasMessages()) {
$return['messages'] = $flashMessenger->getMessages();
}
return $return;
}
If what you need is to read the messages in the same request (something that is also really common) you have to use the "Current Messages" methods of the helper. So, instead of calling
$this->flashMessenger()->hasSuccessMessages()
you should call
$this->flashMessenger()->hasCurrentSuccessMessages()
and instead of calling
$this->flashMessenger()->getSuccessMessages()
you should call
$this->flashMessenger()->getCurrentSuccessMessages()
You can take a look at the full list of functions, if you go to the Zend.Mvc.Controller.Plugin.FlashMessenger API docs and you search for "Current"
UPDATE
To show the messages in the view, you can use 2 approaches:
Retrieve the messages in the controller and send it to the view
In the controller
public function yourAction() {
return array (
'$successMessages'
=> $this->flashMessenger()->getCurrentSuccessMessages()
);
}
In the view
//note that getCurrentSuccessMessages() returns an array,
//so in the vew $successMessages is an array
foreach ($successMessages as $m) {
echo $m;
}
Retrieving directly in the view. The first thing that comes to mind is to use the FlashMessenger view helper (Zend\View\Helper \FlashMessenger), but im afraid it doesnt have a function to get the current messages. So, you can extend it, or even more, you can write your own view helper. You have here a good tutorial on how to do exactly this.
I just tested the following in a Controller:
$this->flashMessenger()->addSuccessMessage('test');
if ($this->flashMessenger()->hasSuccessMessages()) {
print_r($this->flashMessenger()->getSuccessMessages());
}
This resulted in Array ( [0] => test )
Based on your question it appears you have not yet called on getSuccessMessages()
To get the messages into your ViewModel you could do something like this:
$viewModel = new ViewModel();
$viewModel->setVariable('messages', ($this->flashMessenger()->hasSuccessMessages()) ? $this->flashMessenger()->getSuccessMessages() : null);
And in your View you could do this:
if (!is_null($this->messages)) {
foreach ($this->messages as $message) {
echo $message . "\n";
}
}
Try reloading the page a few times to ensure that the message is visible. I personally use the EventManager (in my Controller) to check and handle messages stored in the flashMessenger(). For example:
public function setEventManager(EventManagerInterface $events)
{
parent::setEventManager($events);
$events->attach('dispatch', function () {
$this->getServiceLocator()->get('translator')->setLocale('en');
if ($this->flashMessenger()->hasMessages()) {
$this->Flash()->Display($this->flashMessenger()->getMessages()); // Custom Controller Plugin
}
}, 100);
}
Hope this helps you out!

PHPUnit: Testing form submissions with session variables stored in Symfony2

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());
}
}
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.
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