Laravel testing redirect from function - php

If I have a function that checks to see if a string is numeric and redirects back if it is not but does nothing if it is ie
function check_numeric(string $param) {
if(!is_numeric($param)) {
return Redirect::back()->with('Failed', 'Number!');
}
}
How would I test this in PHPUnit? I have tried to use assertRedirect but I am not sure of how to implement it (if it is even possible)
To be clear. The check_numeric() function is a standalone function that is imported into controllers to be used by the different controller classes. The fucntion itself does not have a class nor a route.
What I would like to do is test the function directly without its use in a controller or route.
I can test the pass cases by doing:
$this->assertNull(check_numeric('1')); // does what I want!!
However I would also like to directly check the fail cases with something like
$previousUrl = '/';
$this->from($previousUrl)->(check_numeric('five'))->assertRedirect($previousUrl);

I think there is not automatic way to check if it's redirecting back, but you can build it.
To approaches comes to mind.
All samples assumed under phpunit, test extending Tests\TestCase;
1 - Check the URL before you make the request
$currentUrl = "/";
$response = $this->post('check-number', "five" );
$response->assertRedirect($currentUrl);
2 - Validate the message you're sending back when the validation fails.
$response = $this->post('/check-number', "five" );
$this->followRedirects($response)->assertSee('Failed');
YourController.php
function check_numeric(string $param) {
if(!is_numeric($param)) {
return Redirect::back()->with('Failed', 'Number!');
}
}
web.php
Route::post('/check-number', 'App\Http\Controllers\YourControllerController#check_numeric');
In the other hand, responding to your previous question, if you want to test the function directly, you have to go with what you're expecting, in this case you're not returning anything if everything is OK, so the way to go on that case would be:
$this->assertNull(app('App\Http\Controllers\YourControllerController')->check_numeric("five"));
This would fail, if you pass a number it would pass.
Doesthat help?

Related

CodeIgniter 4 redirect function not working

After logout, I tried to redirect for the home page. I tried to few ways, but not redirected.
class User extends BaseController
{
public function __construct()
{
helper('url');
}
for the logout function. I used three ways
redirect('/');
or
header("Location:".base_url());
or
route_to('/');
as per CI 4
use
return redirect()->to('url');
if you are using route then use
return redirect()->route('named_route');
I use this and it works
return redirect()->to(site_url());
In codeigniter 4 redirect()->to() returns a RedirectResponse object, which you need to return from your controller to do the redirect.
for ex.
class Home extends BaseController {
public function index() {
return redirect()->to('https://example.com');
}
}
I am new to CI4. In my case, I had to properly set $baseURL in App.php. For example, if the port is set incorrectly in your local development, it will just hang.
eg. public $baseURL = 'http://localhost:8888/';
Its worth saying that unlike the former CI3 redirect() function this one must be called from within a Controller. It won't work for example within a Library.
Update 2021
It is in fact possible to do this! Simply check that the returned response is an object and return it instead. So if a library returns a RedirectResponse, check it using the following code and return if applicable.
if (!empty($log) && is_object($log)){
return $log;
}
You could of course do get_class() to make sure the object is a type of RedirectResponse if there is any possibility of another object being returned.
If you using unnamed route:
$this->response->redirect(site_url('/user'));
'/user': It is my controller name. You can also used controller/function name.
Please look at the documentation
// Go back to the previous page
return redirect()->back();
// Go to specific URI
return redirect()->to('/admin');
// Go to a named route
return redirect()->route('named_route');
// Keep the old input values upon redirect so they can be used by the old() function
return redirect()->back()->withInput();
// Set a flash message
return redirect()->back()->with('foo', 'message');
// Copies all cookies from global response instance
return redirect()->back()->withCookies();
// Copies all headers from the global response instance
return redirect()->back()->withHeaders();
If you find:
{0, string} route cannot be found while reverse-routing
This error:
Please Go to system\HTTP\RedirectResponse Line no 91 :
Change:
throw HTTPException::forInvalidRedirectRoute($route);
To:
return $this->redirect(site_url('/Home'));
(dashboard after login)
The redirect statement in code igniter sends the user to the specified web page using a redirect header statement.
This statement resides in the URL helper which is loaded in the following way:
$this->load->helper('url');
The redirect function loads a local URI specified in the first parameter of the function call and built using the options specified in your config file.
The second parameter allows the developer to use different HTTP commands to perform the redirect "location" or "refresh".
According to the Code Igniter documentation: "Location is faster, but on Windows servers it can sometimes be a problem."
Example:
if ($user_logged_in === FALSE)
{
redirect('/account/login', 'refresh');
}
Original Answer: https://stackoverflow.com/a/725200/5700401

Laravel PHPUnit mock Request

I'm doing a PHPUnit on my controller and I can't seem to mock the Request right.
Here's the Controller:
use Illuminate\Http\Request;
public function insert(Request $request)
{
// ... some codes here
if ($request->has('username')) {
$userEmail = $request->get('username');
} else if ($request->has('email')) {
$userEmail = $request->get('email');
}
// ... some codes here
}
Then on the unit test,
public function testIndex()
{
// ... some codes here
$requestParams = [
'username' => 'test',
'email' => 'test#test.com'
];
$request = $this->getMockBuilder('Illuminate\Http\Request')
->disableOriginalConstructor()
->setMethods(['getMethod', 'retrieveItem', 'getRealMethod', 'all', 'getInputSource', 'get', 'has'])
->getMock();
$request->expects($this->any())
->method('get')
->willReturn($requestParams);
$request->expects($this->any())
->method('has')
->willReturn($requestParams);
$request->expects($this->any())
->method('all')
->willReturn($requestParams);
// ... some codes here
}
The problem here is that when ever I var_dump($request->has('username'); it always return the $requestParams value in which is the whole array. I'm expecting that it should return true as the username key exists in the array.
Then when I delete the username key on the $requestParams, it should return false as it does not contain the username key on the array
Its not ideal to mock Requests, but sometimes you just want to do it anyway:
protected function createRequest(
$method,
$content,
$uri = '/test',
$server = ['CONTENT_TYPE' => 'application/json'],
$parameters = [],
$cookies = [],
$files = []
) {
$request = new \Illuminate\Http\Request;
return $request->createFromBase(
\Symfony\Component\HttpFoundation\Request::create(
$uri,
$method,
$parameters,
$cookies,
$files,
$server,
$content
)
);
}
As far as I can see and understand you're telling your unit test that when you call $request->has() on your request object that it should return the $requestParams array, not true or false, or anything else.
Unless you specifically check what is send with a method call your mock doesn't actually care what is send, it just cares that it was called.
You might want to explore creating an empty request and filling it with data if that is possible in your use case as that'll let you run your unit test with more ease and less issues. This won't work in all cases.
You could include what assertions you're making in your unit test so we can see more clearly what you're running into, but as it is. It returns exactly what you're telling it to return. Even if that's not what you actually want it to return.
Mocks are used to separate your Unit-Test from the rest of your system. As such you usually tend to only check if a specific method is called to see if your code actually exits to the class you mocked and if it has the expected data you'd send along. In some extreme cases you can want to mock the system you're actually testing, but this usually indicates that your code is too dependent on other classes or it's doing too much.
Another reason to use mocks is to satisfy Type Casting constraints in your method calls. In these cases you'll usually create an empty mocked object and fill it with some dummy data your code will accept or break on to test the code.
In your case it seems you want to check if your code actually works correctly and for this I'd suggest either not mocking the request, or making specific tests where you tell it to return true, or false (test for both cases)
So something along the lines of:
$request->expects($this->any())
->method('has')
->with('username')
->willReturn(true); // or false in your next test
Edit:
As you mentioned in the comment Below you ran into the issue that you're using the has method multiple times in your code and ran into issues.
The Questions I've linked to in my response comment go into greater detail but to sum it up, you can use an inline function or the at() method to deal with multiple cases.
With at() you can supply specific iterations of the code to hit only that bit of the test. It has been mentioned that this makes your tests rather brittle as any has added before the previous ones would break the test.
$request->expects($this->at(0))
->method('has')
->with('username')
->willReturn('returnValue');
$request->expects($this->at(1))
->method('has')
->with('email')
->willReturn('otherReturnValue');
The inline function (callback) solution would allow you to customize your test to allow multiple cases and to return data as required. Unfortunately I'm not too familiar with this concept as I haven't used it myself before. I suggest reading the PHPUnit docs for more information about this.
In the end I'd still suggest not mocking the request and instead making an empty request that you'll fill with the data you want to check. Laravel comes with some impressive methods that'll let you manually fill the request with a lot of data you'd usually test against.
For example you can add data (post/get data) by using
request->add(['fieldname' => 'value'])
As a last few pointers I'd like to mention that it seems you use var_dump.
Laravel comes with two of it's own functions that are similar and quite useful in debugging.
You can use dd(); or dump();
dd(); dumps and stops the execution of code, while dump(); just outputs whatever you decide. so you could do dd($request); or dump($request); and see what the variables/class objects/etc holds. It'll even put it in a rather spiffy layout with some Javascript and such to allow you to see what's in it and such. Might want to check it out if you didn't knew it existed.
If you use request()->user() you can set user resolver. It allows you to return user you want. I had the same problem and solution for me was like this:
public function testSomething()
{
$user = User::factory()->create();
request()->setUserResolver(function() use ($user) {
return $user;
});
// Dumped result will be newly created $user
dd(request()->user());
}
A simpler answer than #Ian, if your situation is simpler:
Per https://stackoverflow.com/a/61903688/135114,
if
your function under test takes a $request argument, and
you don't need to do funky stuff to the Request—real route paths are good enough for you
... then you don't need to "mock" a Request (as in, mockery),
you can just create a Request and pass it, e.g.
public function test_myFunc_condition_expectedResult() {
...
$mockRequest = Request::create('/path/that/I_want', 'GET');
$this->assertTrue($myClass->myFuncThat($mockRequest));
}
I was running unit test on a FormRequest child class with Laravel Framework 9.3.0 and get this error:
Error : Call to a member function get() on null
/vendor/symfony/http-foundation/Request.php:676
# code failing
$customRequest->get('parameter');
As you can see in Request class, there are lot of public properties (source code):
public $attributes;
public $request;
public $query;
public $server;
public $files;
public $cookies;
public $headers;
...
This is the way i find to partially mock Request class, example below:
# test code
$this->customRequest = new CustomRequest();
$parameterBag = \Mockery::mock(ParameterBag::class);
$parameterBag->shouldReceive('get')
->with('parameter', \Mockery::any())
->andReturn(null) // anything
;
$this->customRequest->attributes = $parameterBag;

Redirect specific routes to page if they don't exist with laravel 4

I have a route that needs to be redirected to another page if the data they're pulling doesn't exist. The route is:
Route::get('{link}/{data}', 'LinkController#getLink');
Where {link} and {data} are model bound with:
Route::model('link', 'Link');
Route::model('data', 'Data');
As is, when the data for this link doesn't exist it 404's, and if it does exist, it's taken to the page as it should. What I would like to do is redirect to another page if the link would otherwise 404. I've found suggestions on how to do this globally, but I only want it to happen on this one route.
Any ideas?
// Link Controller
public function getLink($linkId, $dataId)
{
if ( is_null($link) or is_null($data) ) {
return Redirect::to('some/path');
}
}
If either of the passed models are null when it hits your controller method, just redirect them. As for your /{link} route that you refer to but don't show code for, do something similar in whatever closure/controller you handle that in.
Get rid of the model binding - you've left the cookie cutter realm.
Route::get('{link}/{data?}', 'LinkController#getLink');
// note I made the data ^ parameter optional
// not sure if you want to use it like this but it's worth pointing out
Do all of the model checking in the controller, something like this:
public function getLink($linkId, $dataId)
{
$link = Link::find($linkId);
$data = Data::find($dataId);
if(is_null($link)){
throw new NotFoundHttpException;// 404
}
elseif(is_null($data)){
return Redirect::to('some/view');// redirect
}
// You could also check for both not found and handle that case differently as well.
}
It's hard to tell from your comments exactly how you'd like to treat missing link and/or data records, but I'm sure you can figure that out logically. The point of this answer is that you don't need to use Laravel's model binding since you can do it yourself: find the record(s) else redirect or 404.

What's the proper approach to testing controllers in Laravel?

I'm rewriting an existing Laravel 4 application to ensure that there is adequate testing. Long story short, I've rewritten my AccountController class using TDD methods and I'm running into a bit of a headache.
Consider the following method that renders a page including a list of users:
public function getIndex()
{
// build the view
//
return \View::make('account.list-users')
->with('users', \Sentry::getUserProvider()->findAll());
}
I'm using Smarty to render my views and Sentry for authentication.
Now, I want to write some tests like this:
public function test_getIndex()
{
// arrange
//
// set up some mocks here...
// act
//
$response = $this->client->request("GET", "/list-users");
// assert
//
// test for <table class="table">
$this->assertFalse($response->filter("table.table")==null, "table not found");
// test for some <a> tags for the "update" buttons
$element = $response->filter("td a")->first()->extract(array("href", "class", "_text"));
$this->assertTrue(strstr($element[0][0],"/my-update-url")!="");
$this->assertTrue(strstr($element[0][1],"btn btn-xs btn-success")!="");
$this->assertTrue(strstr($element[0][2],"Active")!="");
// test for some other markup...
}
I've been following Jeffrey Way's book Laravel Testing Decoded and written tests like the one above, and they work fine.
The headache comes up in the "set up some mocks here..." section. Specifically, the number of mocks I need to set up is ridiculous. This is because, as part of a larger web application, I'm using a View composer which adds data to the View model: the current user model, a menu structure, alert messages, news messages, the application version number, etc. I've cut out much of this by using a "bare bones" template for testing, but it's still a lot of stuff - to the point where I'm writing hundreds of lines of code to test this simple one-line method.
Is there a better way of doing this?
The way I see it, there are two ways of doing this:
A. The way I have been doing it
B. Mocking the \View::make call so that all of my template rendering is bypassed - something like this
public function test_getIndex()
{
// arrange
//
$userList = "this is a list of users";
$userProvider = Mockery::mock("\Cartalyst\Sentry\Users\Eloquent\Provider");
\Sentry::shouldReceive("getUserProvider")
->once()
->andReturn($userProvider);
$userProvider->shouldReceive("findAll")
->once()
->andReturn($userList);
$view = Mockery::mock("\Illuminate\View\View");
\View::shouldReceive("make")
->with("account.list-users")
->once()
->andReturn($view);
$view->shouldReceive("with")
->with("users", $userList)
->once()
->andReturn($view);
$view->shouldReceive("render")
->once()
->andReturn("results");
// act
//
$response = $this->call("GET", "/list-users");
// assert
//
$this->assertResponseOk();
}
If I take this approach, the testing is much simpler and I'm only testing the code that's actually in the controller method, but then I'm not really testing everything involved in calling that route (which might be a good thing or might not - I'm not sure) and I worry that I won't get adequate coverage.
So, what's the best way of doing this: (A), (B), or something else?
Edit
There's a fair amount of confusion on my part regarding the testing of my controller method, made clearer by #TheShiftExchange's answer & comments below. I'm going to try to address the issue here, as an edit, because it gives me a little more room to discuss the question.
Consider the second example given in the answer below:
public function testMethod()
{
$this->call('GET', '/list-users');
$this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}
If I run this test, it will work, but it will access the database, which I was trying to avoid by mocking some stuff.
So, I could expand this test a little:
public function testMethod()
{
\Sentry::shouldReceive("getUserProvider")
->once()
->andReturn($userProvider);
// plus a mock of the UserProvider class,...
$this->call('GET', '/list-users');
$this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}
This test will not work because, in addition to the mocks that are required for the controller method, I'll also need mocks for the code in my View composer. This code includes, among other things, $currentUser = \Sentry::getUser() (the user's name is displayed in the upper right hand corner of my application's pages).
So the code actually becomes:
public function testMethod()
{
\Sentry::shouldReceive("getUserProvider")
->once()
->andReturn($userProvider);
// plus a mock of the UserProvider class,...
// plus a mock of ThisClass
// and a mock of ThatClass
// and a mock of SomeOtherClass
// etc.
// etc.
$this->call('GET', '/list-users');
$this->assertViewHas('users', \Sentry::getUserProvider()->findAll());
}
And it quickly gets out of hand.
This suggests to me that I'm doing something wrong, but I'm not sure what. I suspect that the problem stems from my uncertainty about what exactly I'm testing here.
So, after all that, the question becomes this:
What am I really trying to test when I'm testing a controller's methods?
The code in the controller's method? Or,
The whole process from request to response?
What I want to test is the first item - just the code in the controller method. The example in my question is pretty simple, but I do have some controller methods that do things like form validation or redirect based on user input - I'd like to test that code.
Maybe, rather than testing the code via $this->call(), I need to simply call the controller method directly?
As part of the Laravel framework, it includes some testing helpers. Including in these helpers are view testing helpers:
Asserting A View Has Some Data
public function testMethod()
{
$this->call('GET', '/');
$this->assertViewHas('name');
$this->assertViewHas('age', $value);
}
So you could do something like:
public function testMethod()
{
\Sentry::shouldReceive("getUserProvider")
->once()
->andReturn('foo');
$this->call('GET', '/');
$this->assertViewHas('users', 'foo');
}

Can Symfony simply reload a page request?

I have an app that receives a request from another app. It detects a value on the query string, checks that value against a cached value and, if they don't match, it needs to clear its cache and reload the page (establishing a new cache). Unfortunately, I can't find a way to tell Symfony to redirect to the current page in exactly the same format (protocol, URI path, query string, etc.). What am I missing? This is all happening in a filter on isFirstCall().
Thanks.
We have done this in a filter.
It is a bit hacky but here is an example of doing the redirect in a filter...you'll have to do the testing of the cache yourself...
class invalidateCacheFilter extends sfFilter {
public function execute($filterChain) {
$redirect=true;
if($redirect===true)
{
$request = $this->getContext()->getRequest();
/**
This is the hacky bit. I am pretty sure we can do this in the request object,
but we needed to get it done quickly and this is exactly what needed to happen.
*/
header("location: ".$request->getUri());
exit();
}
$filterChain->execute();
}
}
If you want to redirect, you can do like this:
if (true===$redirect)
{
return $this->getContext()->getController()->redirect($request->getUri());
}

Categories