How to get response from action dispatched in phpunit - php

I am dispatching some POST data to an action of a controller. That action echoes some json-encoded string. I want to verify that the json-encoded string of that action is as I want it. I want to know how I can get that string?
My test looks like this:
$this->request->setMethod('POST')
->setPost(['test' => 'databaseschema_Database']);
$params = ['action' => 'analysis', 'controller' => 'Index', 'module' => 'default'];
$urlParams = $this->urlizeOptions($params);
$url = $this->url($urlParams);
$result = $this->dispatch($url);
$this->assertJsonStringEqualsJsonString(
$result, json_encode(["status" => "Success"])
);
My test is failing and I am getting following message:
1) IndexControllerTest::testAnalysisAction
Expected value JSON decode error - Unknown error
stdClass Object (...) does not match expected type "NULL".
Can any one guide me on how to do this?

If you want to do unit testing, what you really want to do is extract the json encoding into it's own class (or a method inside a utils class or something) and then test those method instead of your whole controller.
The problem with your approach is that when running phpunit, there is not $_POST array. The code above does not show what is happening, but I guess there is different behaviour when run through apache and cli which causes your test to fail.
I would create a TransformerClass and test this in isolation:
class JsonTransformer
{
public function transformPostData(array $postArray)
{
// transformation happening
}
}
class JsonTransformerTest extends \PHPUnit_Framework_TestCase
{
public function testTransformPostData()
{
$transformer = new JsonTransformer();
$data = array('action' => 'analysis', 'controller' => 'Index', 'module' => 'default');
$result = $transformer->transformPostData(data);
$this->assertJsonStringEqualsJsonString($result, json_encode(array("status" => "Success")));
}
}
If you need to test your whole request/response, you would use some kind of HTTPClient, request the url, send the post data and see if the response is what you'd expect.
Everything in between (like faking the post data) leaves you with more problems and more code to maintain than it does you good.

Related

PHPUnit how to write a Laravel Nova Observer test

I would like to write a test for my CommentObserver. This observer is only registered in the NovaServiceProvider but not the AppServiceProvider. This means I cannot test my observer by using my own Controllers.
In my eyes I have 3 ways to test my observer:
Either performing a feature test by sending a post request to the Nova API
Mocking the observer by calling the function in the observer to check if the function perfoms as desired
Trying to register my observer on the fly in the AppServiceProvider, performing a request and deregistering the observer in the AppServiceProvider again.
I tried to find a solution for any of these 3 ways to test my observer but unfortunately I faild with any of them.
Problems:
For way 1 I always get a validation error and Nova tells me that my input is invalid.
For way 2 I fail at mocking the observer function
For way 3 I didn't find any solution on how to register and deregister the oberserver on the fly at the AppServiceProvider
Do you guys have idea and solition on how I can test my CommentObserver (which is as written above only registered in my NovaServiceProvider).
Update:
So, here is the code of my observer. I need to have an valid request to test my observer in order to have the ability to access the $request->input('images') variable. I do know I can also use $comment->content instead of request()->input('content') because $comment->content already contains the new content which is not saved it this point.
The reason why I need a valid request is that the variable images is not part of the Comment model. So I cannot use $comment->images because it simply doesn't exist. That's why I need to access the request input. What my observer is basically doing is to extract the base64 images from the content, saves them to the server and replaces them by an image link.
class CommentObserver
{
public function updating(Comment $comment)
{
if (!request()->input('content')) {
return;
}
if (request()->input('content') == $comment->getRawOriginal('content')) {
return;
}
$images = request()->input('images');
if(!is_array($images)) {
$images = json_decode(request()->input('images'));
}
checkExistingImagesAndDeleteWhenNotFound($comment, request()->input('content'), 'comments', 'medium');
$comment->content = addBase64ImagesToModelFromContent($comment, request()->input('content'), $images, 'comments', 'medium');
}
}
This is my test so far. I choose way 1 but as described already this always leads to an validation error by the nova controller and I cannot figure out what is the error/what is missing or wrong.
class CommentObserverTest extends TestCase
{
/** #test */
public function it_test()
{
$user = User::factory()->create([
'role_id' => Role::getIdByName('admin')
]);
$product = Product::factory()->create();
$comment = Comment::factory()->create(['user_id' => $user->id, 'content' => '<p>Das ist wirklich ein super Preis!</p>', 'commentable_type' => 'App\Models\Product', 'commentable_id' => $product->id]);
$data = [
'content' => '<p>Das ist wirklich ein HAMMER Preis!</p>',
'contentDraftId' => '278350e2-1b6b-4009-b4a5-05b92aedaae6',
'pageStatus' => PageStatus::getIdByStatus('publish'),
'pageStatus_trashed' => false,
'commentable' => $product->id,
'commentable_type' => 'App\Models\Product',
'commentable_trashed' => false,
'user' => $user->id,
'user_trashed' => false,
'_method' => 'PUT',
'_retrieved_at' => now()
];
$this->actingAs($user);
$response = $this->put('http://nova.mywebsiteproject.test/nova-api/comments/' . $comment->id, $data);
dd($response->decodeResponseJson());
$das = new CommentObserver();
}
}
Kind regards and thank you
Why depend on the boot method in your NovaServiceProvider? It is possible to call the observe() method on the fly in your test:
class ExampleTest extends TestCase
{
/** #test */
public function observe_test()
{
Model::observe(ModelObserver::class);
// If you need the request helper, you can add input like so:
request()->merge([
'content' => 'test'
]);
// Fire model event by updating model
$model->update([
'someField' => 'someValue',
]);
// Updating should be triggered in ModelObserver
}
}
It should be now be possible in your observer class:
public function updating(Model $model)
{
dd(request()->input('content')); // returns 'test'
}

PHP - Calling internal API

I am a newbie in PHP.
I created a Laravel project using *composer**.
My controller has two endpoint uploadFile and testpost:
public function uploadFile(Request $request) {
//there are more code about reading uploaded file here. Everything is OK here.
$request = Request::create('/api/testpost', 'POST',
[],[],[],[],'{"this is" : "my test content"}');
return Route::dispatch($request);
}
public function testpost(Request $request){
Log::info($request->all());
return response()->json(["title"=>"this is the test get method"]);
}
uploadFile is invoked by POST action from a form which carries an uploaded JSON file.
I want to call testpost inside of uploadFile method using Request::create(...) and Route::dispatch(...).
testpost is invoked however the body of request is not as expected. The log file shows me that $request->all() does not return the request body which I expect to be {"this is" : "my test content"}.
My log file:
[2019-02-23 12:16:47] local.INFO: array (
'_token' => 'JzQjclRD4WaTkezqLxlU48D1dM7S3X2X3hok3kr4',
'employee_file' =>
Illuminate\Http\UploadedFile::__set_state(array(
'test' => false,
'originalName' => 'test_input_file.txt',
'mimeType' => 'text/plain',
'error' => 0,
'hashName' => NULL,
)),
)
What wrong in my code? API invocation or request body retrieval?
I know that we can call testpost method directly instead of calling API. However, I ultimate purpose is to know how to call an internal API.
You don't need to use internal API calls for this.
If both methods are in the same class you can invoke directly with
$this->methodName($args);
and it will return the result directly to the calling function. (provided you have a return statement in the method you are invoking)

Symfony 2 - Call controller from another controller

I would like use a method of controller from another bundle, in my controller.
The method this->forward need a Response object, and i don't know how to use it.
public function indexAction($name)
{
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green',
));
// ... further modify the response or return it directly
return $response;
}
And i saw that i can use service but i want to know if its the best solution or they are another.
$this->forward takes arguments in this order:
Logical Name of controller action in string format i.e. 'AcmeHelloBundle:Hello:fancy'
Parameters to be passed as request variables in array format i.e. array(
'name' => $name,
'color' => 'green',
)
These parameters can be accessed in the controller using request access functions.
Sometimes you want to bypass security completely and run a command in another controller despite a user's permissions level. Luckily you can do that fairly easily.
First, run a use Command for your controller at the top of the controller you want to use the data from:
use AppBundle\Controller\MySourceDataController;
Then call that function from within your destination controller:
$response = MySourceDataController::getTheData( $option1, $option2 );
If you need to pass a Request object, you can do it this way:
$response = MySourceDataController::getTheData( new Request( array(
'server' => 'USAServer1',
) ), $option2 );
This returns a Request with the set parameter of server. I also defined a $option2, this would be a variable often defined in the URL such as:
* #Route("/mydata/{server}/", name="api-source-data")
* #param Request $request
* #param $server
Lastly, if you're passing JSON in that controller and want to convert it back to an object, you can run this bit of code on the $response:
if ( 0 === strpos( $response->headers->get( 'Content-Type' ), 'application/json' ) ) {
$response = json_decode( $response->getContent(), true );
}
Voila. Access any controller form any other controller and bypass security notation for the source controller. :)

Sending a POST request with PHPUnit

I have a symfony website, and Im trying to do some unit testing. I have this kind of test where I try to submit something:
<?php
namespace Acme\AcmeBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class HomeControllerTest extends WebTestCase {
public function testrandomeThings() {
$client = static::createClient();
$crawler = $client->request(
'POST',
'/',
array(
"shopNumber" => 0099,
"cardNumber" => 231,
"cardPIN" => "adasd"),
array(),
array());
}
but I dont think that the data Im sending is being received in the controler:
class HomeController extends Controller
{
public function indexAction()
{
var_dump($_POST);
die;
return $this->render('AcmeBundle:Home:index.html.twig');
}
}
the var_dump is actually returning me an empty array.
What am I missing to send information through my POST request?
$_POST is a variable filled by PHP and the symfony request is only created from this globals if called directly over http. The symfony crawler doesn't make a real request, it creates a request from the parameters supplied in your $client->request and executes it. You need to access this stuff via the Request object. Never use $_POST, $_GET, etc. directly.
use Symfony\Component\HttpFoundation\Request;
class HomeController extends CoralBaseController
{
public function indexAction(Request $request)
{
var_dump($request->request->all());
die;
return $this->render('CoralWalletBundle:Home:index.html.twig');
}
}
use $request->request->all() to get all POST parameters in an array. To get only a specific parameter you can use $request->request->get('my_param'). If you ever need to acces GET parameters you can use $request->query->get('my_param'), but better set query parameters already in the routing pattern.
I think you're trying to do this:
$client = static::createClient();
$client->request($method, $url, [], [], [], json_encode($content));
$this->assertEquals(
200,
$client->getResponse()
->getStatusCode()
);
You're putting your data (content) in as the params array but you want to put it in as the raw body content which is a JSON encoded string.

ZF2: Return JSON only for Ajax Call

I'm trying to learn ZF2. I have a page that uses Ajax to get some data. The ZF2 function should return an JSON string.
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\View\Model\JsonModel;
class DocumentsController extends AbstractActionController {
public function indexAction() {
}
public function getTreeDataAction() {
$json = new JsonModel(array(
'title' => 'Some Title'
));
return $json;
}
}
But I keep getting this Fatal Error:
( ! ) Fatal error: Uncaught exception 'Zend\View\Exception\RuntimeException' with message 'Zend\View\Renderer\PhpRenderer::render: Unable to render template "application/documents/get-tree-data"; resolver could not resolve to a file' in ../vendor/ZF2/library/Zend/View/Renderer/PhpRenderer.php on line 451
I have been searching around for this error and the best way to make ajax calls in ZF2, however results for ZF1 or ZF2 betas keep coming up and do not work. Thank you for any advice you can give.
Hmm, that error pretty much implies that it tries to access the default rendering strategy, which is quite weird... Have you added the JsonStrategy to your view_manager?
//module.config.php
return array(
'view_manager' => array(
'strategies' => array(
'ViewJsonStrategy',
),
),
)
Furthermore it's a good idea to set the correct accept header for within you ajax calls to only accept application/json content type. With this set, it should actually work. Out of curiousity though, does modules/__NAMESPACE__/view/__namespace__/documents/get-tree-data.phtml exist?
Try something like this...
$response = $this->getResponse();
$response->setStatusCode(200);
$jsonArray = {.....}
$response->setBody($jsonArray);
return $response;
And make sure you add ViewJsonStrategy to your module config as well.

Categories