I have an old Laravel project that uses conception for tests
And there are two APIs that when I call the first one, it returns a specific resource as shown below:
class FirstResource extends JsonResource
{
public function toArray($request)
{
self::withoutWrapping();
return [
....
];
}
}
And the second API returns a resource as shown below:
class SecondResource extends JsonResource
{
public function toArray($request)
{
return [
....
];
}
}
JsonResource code (Laravel source code):
class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable
{
/**
* The "data" wrapper that should be applied.
*
* #var string|null
*/
public static $wrap = 'data';
//...
}
The problem is that when these calls are made, the $wrap property in the second resource becomes null, even though its value should be equal to 'data', causing the test to fail.
Actually, the first resource affects the second resource on separate APIs!!!
Why it happens ?
I think Laravel-module makes just one instance of the app for all tests, so this conflict happens, am I right?
It can be fixed?
Related
How to extend Symfony\Component\HttpFoundation\Request and create my own Request correctly?
I tried to create my own request:
class ChangeRequest extends Request
But, when I used it as argument in controller action, I get an error
Argument #1 ($request) must be of type App\Request\ChangeRequest, Symfony\Component\HttpFoundation\Request given
I want to create several requests extends Symfony\Component\HttpFoundation\Request, and use this requests in different controller actions. Every request contains JSON data and has its own validate rules. Now I use ArgumentValueResolverInterface for resolving this requests.
class RequestResolver implements ArgumentValueResolverInterface
{
/**
* #inheritDoc
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
$type = $argument->getType();
return class_exists($type) && str_starts_with($type, 'App\Request');
}
/**
* #inheritDoc
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield new ($argument->getType())($request);
}
}
After that I edited services.yaml and the error disappeared:
App\Resolver\RequestResolver:
tags:
- { name: controller.argument_value_resolver, priority: 50 }
Is this the right way?
I'm trying to mock a method in the Model so that I am able to test the controller api endpoint with different scenarios.
I am currently using Laravel 8 with PhpUnit and Mockery package.
In the route I am using Route model binding.
api.php
Route::get('/api/{project}', [ProjectController::class, 'show']);
ProjectController.php
class ProjectController extends Controller
{
public function show(Project $project)
{
$state = $project->getState();
return response()->json($state);
}
}
ProjectControllerTest.php
class ProjectControllerTest extends TestCase
{
/**
* #test
* #group ProjectController
*/
public function shouldReturn200ForValidProjectGuid()
{
$project = Project::factory()->create();
$mock = Mockery::mock(Project::class);
// I have also tried this, see error below
// $mock = Mockery::mock(Project::class)->makePartial();
$this->app->instance(Project::class, $mock);
$mock->shouldReceive('getState')->andReturn('New South Wales');
$response = $this->getJson("/api/{$project->guid}");
$response->assertStatus(200)
->assertJson([
'data' => 'New South Wales'
]);
}
}
I am currently getting this error
Received Mockery_0_App_Models_Project::resolveRouteBinding(), but no expectations were specified Exception caught at [/usr/src/app/vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php(34) : eval()'d code#924
I have tried other options like makePartial() instead, however it results in the following error as well
Received Mockery_0_App_Models_Project::__construct(), but no expectations were specified Exception caught at [/usr/src/app/vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php(34) : eval()'d code#924
Any help would be much appreciated, thank you
You can try the following. I have changed the controller response slightly to make it work with assertJson().
web.php
Route::get('/projects/{project}', [ProjectController::class, 'show']);
ProjectController
<?php
namespace App\Http\Controllers;
use App\Models\Project;
class ProjectController extends Controller
{
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show(Project $project)
{
return response()->json([
'state' => $project->getState()
]);
}
}
ProjectControllerTest
<?php
namespace Tests\Feature;
use App\Models\Project;
use Mockery;
use Tests\TestCase;
class ProjectControllerTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function test_example()
{
$mock = Mockery::mock(Project::class)->makePartial();
$mock->shouldReceive('resolveRouteBinding')->andReturnSelf();
$mock->shouldReceive('getState')->andReturn('NSW');
$this->app->instance(Project::class, $mock);
$response = $this->get('/projects/1');
$response->assertStatus(200)
->assertJson([
'state' => 'NSW'
]);
}
}
You don't mock models, you are already using a Factory so you still use that to create "mock" data on your model.
$project = Project::factory()->create(['state' => 'New South Wales']);
Read more about factories and unit testing in Laravel. See the official documentation.
I have the following method in my Laravel controller:
public function specialOffers($id) {
return \App\Http\Resources\SpecialOfferResource::collection(Offers::all());
}
I need some special manipulations, so I've created this SpecialOfferResource resource. The resource code is:
class SpecialOfferResource extends Resource {
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request) {
//here I need the $id passed to the controller's method,
//but I only have $request
return [
//my request fields, everything ok
];
}
}
How can I pass $id from the controller's method to this resource? I know I can pass through the request as a field, but is it possible this other way?
The resource collection is just a wrapper that formats, or maps, the collection you pass to it.
The collection you are passing is Offers::all(), which would include all Offers models.
You'd likely want to use the query builder to narrow down the collection you are passing:
public function specialOffers($id) {
$results = Offers::where('column', $id)->get();
return \App\Http\Resources\SpecialOfferResource::collection($results);
}
I'm not sure whether this is acceptable or not, but in some case i do need some parameter passed from controller to use inside toArray resource method and this is what i did.
Create resource class that extend Illuminate\Http\Resources\Json\ResourceCollection.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class TestResource extends ResourceCollection
{
private $id;
public function __construct($id, $collection)
{
parent::__construct($collection);
$this->id = $id;
}
public function toArray($request)
{
return [
'data' => $this->collection,
'id' => $this->id
];
}
}
And from controller you can call like this:
<?php
namespace App\Http\Controllers;
use App\Http\Resources\TestResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class TestController extends Controller
{
public function index()
{
$id = 30;
$collection = collect([['name' => 'Norli'], ['name' => 'Hazmey']]);
return new TestResource($id, $collection);
}
}
I'm testing one of my controllers and no matter what I try I get the error that the all() function doesn't exist.
Static method Mockery_1_App_Models_User::all() does not exist on this mock object
My test method:
/**
* Test index page
* #return void
*/
public function testIndexAsUser()
{
$this->beUser();
// The method calls the mock objects should receive
$this->user->shouldReceive('all')->once()->andReturn([]);
// Call index page
$response = $this->call('GET', 'users');
// Assertions
$this->assertResponseOk();
$this->assertViewHas('user');
$this->assertViewNameIs('users.show');
}
My mocking method:
/**
* Mock a class
* #param string $class
* #return Mockery
*/
public function mock($class)
{
$mock = Mockery::mock('Eloquent', $class);
app()->instance($class, $mock);
return $mock;
}
My actual controller method:
/**
* Show all users
* #return Response
*/
public function getIndex()
{
$users = $this->user->all();
return view('users.index');
}
Am I using the wrong Eloquent class in my mock object or something? Since Laravel 5 the models are not referring to Eloquent but to Illuminate\Database\Eloquent\Model but I've tried that too.
The easiest way to mock an Eloquent model is by using partials:
$mock = m::mock('MyModelClass')->makePartial();
However, it won't help you much as you're using a static method (all()). PHP's not-so-strict nature lets you call static methods in a non-static way ($user->all()), but you should avoid it. Instead you should do it the heavy-handed way:
$users = $this->user->newQuery()->get();
This can be mocked:
$mockUser->shouldReceive('newQuery->get')->andReturn([/*...*/]);
If you want to take this one step further, move the get() call into a separate repository class that is injected into the controller, which will make it easier to mock. You can find plenty of articles on the repository pattern online.
I am currently looking a this piece of code from a module called ZfcUser for Zend 2:
namespace ZfcUser\Controller;
use Zend\Form\Form;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Stdlib\ResponseInterface as Response;
use Zend\Stdlib\Parameters;
use Zend\View\Model\ViewModel;
use ZfcUser\Service\User as UserService;
use ZfcUser\Options\UserControllerOptionsInterface;
class UserController extends AbstractActionController
{
/**
* #var UserService
*/
protected $userService;
.
.
public function indexAction()
{
if (!$this->zfcUserAuthentication()->hasIdentity()) {
return $this->redirect()->toRoute('zfcuser/login');
}
return new ViewModel();
}
.
.
}
In the namespace ZfcUser\Controller\Plugin:
namespace ZfcUser\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\ServiceManager\ServiceManager;
use ZfcUser\Authentication\Adapter\AdapterChain as AuthAdapter;
class ZfcUserAuthentication extends AbstractPlugin implements ServiceManagerAwareInterface
{
/**
* #var AuthAdapter
*/
protected $authAdapter;
.
.
/**
* Proxy convenience method
*
* #return mixed
*/
public function hasIdentity()
{
return $this->getAuthService()->hasIdentity();
}
/**
* Get authService.
*
* #return AuthenticationService
*/
public function getAuthService()
{
if (null === $this->authService) {
$this->authService = $this->getServiceManager()->get('zfcuser_auth_service');
}
return $this->authService;
}
My Questions:
From indexAction(), the controller plugin is called without being instantiated ($this->zfcUserAuthentication()->hasIdentity()), do controller plugins always work like this?.
What really happens in the hasIdentity()? I see getAuthService() returning something but not hasIdentity().I am not familiar with this type of advanced class implementation of function calling so I would truly appreciate any explanation here or topic I should look into.
I can't answer your first question, but regarding your second question:
The getAuthService() method in your code returns an AuthenticationService object, which has a hasIdentity() method.
So there are two different hasIdentity() methods:
In the AuthenticationService class (source code here).
In the ZfcUserAuthentication class which you're looking at.
This line of code in the ZfcUserAuthentication class:
return $this->getAuthService()->hasIdentity();
does three things:
$this->getAuthService() returns an AuthenticationService object.
The hasIdentity() method of that AuthenticationService object is then called, and it returns a boolean.
That boolean is then returned.
Imagine splitting the code into two parts:
// Get AuthenticationService object Call a method of that object
$this->getAuthService() ->hasIdentity();
Hope that helps!
All sorts of plugins in Zend Framework are managed by plugin managers, which are subclasses of AbstractPluginManager which is subclasss of ServiceManager.
$this->zfcUserAuthentication() proxies by AbstractController to pluginmanager internally.
AuthenticationService::hasIdentity() checks if something was added to storage during successful authentication attempt in this or previous request:
See here