I'm having trouble implementing Fractal includes. I am trying to include posts with a particular user.
All goes well when I add 'posts' to $defaultIncludes at the top of my UserItemTransformer. Posts are included as expected.
However, posts are NOT included in my json output when I change $defaultIncludes to $availableIncludes, even after calling $fractal->parseIncludes('posts');
The problem seems to lie the fact that the method that includes the posts is only called when I use $defaultIncludes. it is never called when I use $availableIncludes.
I'm probably missing something obvious here. Can you help me find out what it is?
This works:
// [...] Class UserItemTransformer
protected $defaultIncludes = [
'posts'
];
This does not work:
// [...] Class UserItemTransformer
protected $availableIncludes = [
'posts'
];
// [...] Class PostsController
// $fractal is injected in the method (Laravel 5 style)
$fractal->parseIncludes('posts');
Got it!
When I called parseIncludes('posts'), this was on a new Fractal instance, injected into the controller method. Of course I should have called parseIncludes() on the Fractal instance that that did the actual parsing (and that I injected somewhere else, into an Api class).
public function postsWithUser($user_id, Manager $fractal, UserRepositoryInterface $userRepository)
{
$api = new \App\Services\Api();
$user = $userRepository->findById($user_id);
if ( ! $user) {
return $api->errorNotFound();
}
$params = [
'offset' => $api->getOffset(),
'limit' => $api->getLimit()
];
$user->posts = $this->postRepository->findWithUser($user_id, $params);
// It used to be this, using $fractal, that I injected as method parameter
// I can now also remove the injected Manager $fractal from this method
// $fractal->parseIncludes('posts');
// I created a new getFractal() method on my Api class, that gives me the proper Fractal instance
$api->getFractal()->parseIncludes('posts');
return $api->respondWithItem($user, new UserItemTransformer());
}
I'll just go sit in a corner and be really quit for a while, now.
Related
I registered LogConnectionFailed like this:
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
'Illuminate\Http\Client\Events\ConnectionFailed' => [
'App\Listeners\LogConnectionFailed',
],
];
The ConnectionFailed event is fired if no response is received for a given request.
my class {
public function send() {
$response = Http::get('http://example.com');
}
}
I need to The name of the class and the method in which this happened and duration time call http client in LogConnectionFailed class.
This is not possible through normal parameter passing, so I utilized PHP native function debug_backtrace() and hacked through it.
The logic is that when the listener wants to handle the event, we get the callback trace and filter through the call stack frames until we find one of our watching location.
The code is this:
use Illuminate\Support\Str;
use Illuminate\Http\Client\Events\ConnectionFailed;
class LogConnectionFailed
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
public function handle(ConnectionFailed $event)
{
$backtraceCollection = Collect(debug_backtrace());
$callerClass = $backtraceCollection->first(function($value, $key) use ($event){
$class = $value['class'] ?? '';
return $this->classIsWatched($class);
});
if ($callerClass) {
// Store in DB or do some other stuff.
dd([
'class' => $callerClass['class'],
'function' => $callerClass['function'],
'line' => $callerClass['line'],
]);
} else {
dd("should skip. Not Watching classes.");
}
}
private function classIsWatched(string $className): bool
{
return Str::is(
['App\Http\Controllers\*', 'App\MyClass'],
$className
);
}
}
Here take note at the array inside the function classIsWatched:
['App\Http\Controllers\*', 'App\MyClass']
These are the classes or directories we will watch, which means if the ConnectionFailed due to some calls from these classes, they will be captured, else they will be skipped. This gives you the flexibility to just filter out and watch certain locations inside your application.
Note that we can also use wildcards * for simplifying the path inclusions. For example App\Http\Controllers\Api\EventController is watched too.
For example if I have this class inside the App path:
namespace App;
use Illuminate\Support\Facades\Http;
class MyClass
{
public static function callEvent()
{
$response = Http::get('http://example.com');
}
}
due to any reason if a ConnectionFailed event dispatches, the output of handle method will be:
array:3 [▼
"class" => "App\MyClass"
"function" => "callEvent"
"line" => 11
]
this will give you the class name, function name and even the line which event was raised there. You can simply replace the dd() inside the handle method of the listener and do what you want to do with the data.
About the Http Call duration, no accurate solution comes to my mind, but you can have a rough estimation using this approach:
dd(microtime(true) - LARAVEL_START);
add the above code inside the handle method too, and this gives you the time difference from the moment that the application started and till you got this point (Http request failed and you got inside this listener).
I'm trying to unit testing a service that handles the registration of a user in Laravel.
This is the service:
public function completeRegistration(Collection $data)
{
$code = $data->get('code');
$registerToken = $this->fetchRegisterToken($code);
DB::beginTransaction();
$registerToken->update(['used_at' => Carbon::now()]);
$user = $this->userRepository->update($data, $registerToken->user);
$token = $user->createToken(self::DEFAULT_TOKEN_NAME);
DB::commit();
return [
'user' => $user,
'token' => $token->plainTextToken,
];
}
Where the update method has the following signature:
<?php
namespace App\Repositories\User;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
interface UserRepositoryInterface
{
public function create(Collection $data): User;
public function update(Collection $data, User $user): User;
}
With my test being:
/**
* Test a user can register
*
* #return void
*/
public function test_user_can_complete_registration()
{
$userRepositoryMock = Mockery::mock(UserRepositoryInterface::class);
$registerTokenRepositoryMock = Mockery::mock(RegisterTokenRepository::class);
$userFactory = User::factory()->make();
$registerTokenFactory = RegisterToken::factory()
->for($userFactory)
->timestamped()
->make(['user_id' => $userFactory->id]);
dd($registerTokenFactory->user);
$userRepositoryMock
->expects($this->any())
->once()
->andReturn($userFactory);
....
}
When I run phpunit --filter=test_user_can_complete_registration I get the following error:
1) Tests\Unit\Services\Auth\AuthServiceTest::test_user_can_complete_registration
TypeError: Argument 2 passed to Mockery_0_App_Repositories_User_UserRepositoryInterface::update() must be an instance of App\Models\User, null given, called in /var/www/app/Services/Auth/AuthService.php on line 64
/var/www/app/Services/Auth/AuthService.php:64
/var/www/tests/Unit/Services/Auth/AuthServiceTest.php:88
This tells me that the user relationship on $registerTokenFactory is null. When I do:
public function test_user_can_complete_registration()
{
...
dd($registerTokenFactory->user);
}
I get the output null. I'm trying to test the service without hitting the database. How can I attach the user relationship to the $registerTokenFactory object? I have tried using for and trying to attach directly:
$registerTokenFactory = RegisterToken::factory()
->for($userFactory)
->timestamped()
->make(['user_id' => $userFactory->id, 'user' => $userFactory]);
In Laravel factories make() does only create the model and does not save it. For relationship to work, you will need your models to be saved.
$userFactory = User::factory()->create();
Since you do not want to use a Database, which is wrong in my opinion. People don't like writing tests, so when we have to do it make it simple, mocking everything to avoid databases is a pain. Instead an alternative is to you Sqlite to run in memory, fast and easy. A drawback is some functionality does not work there JSON fields and the version that are in most Ubuntu distributions does not respect foreign keys.
If you want to follow the path you are already on, assigned the user on the object would work, you have some left out bits of the code i assume.
$userRepositoryMock = Mockery::mock(UserRepositoryInterface::class);
$registerTokenRepositoryMock = Mockery::mock(RegisterTokenRepository::class);
$user = User::factory()->make();
$registerToken = RegisterToken::factory()
->for($userFactory)
->timestamped()
->make(['user_id' => $user->id]);
$registerToken->user = $user;
$registerTokenRepositoryMock
->expects('fetchRegisterToken')
->once()
->andReturn($registerToken);
$userRepositoryMock
->expects($this->any())
->once()
->andReturn($user);
// execute the call
I'm new to Mockery. I'm trying to figure it out with the GitHub API by using a Laravel Package as a wrapper. How can I mock GitHub::repo()->show('symfony', 'demo'); without hiting the actual API? Is there something weird with Facades? I'm getting an error here:
In EvalLoader.php(34) : eval()'d code line 993:
Cannot redeclare Mockery_0_GrahamCampbell_GitHub_Facades_GitHub::shouldReceive()
Code:
use Mockery;
use Tests\TestCase;
use GrahamCampbell\GitHub\Facades\GitHub;
public function testExample()
{
$this->mockGitHubWith([
'id' => 1,
'name' => 'demo',
'full_name' => 'symfony/demo',
]);
$repo = GitHub::repo()->show('symfony', 'demo');
dd($repo);
}
protected function mockGitHubWith($expectations)
{
$github = Mockery::mock(GitHub::class, $expectations);
$github->shouldReceive('api')->andReturn($github);
app()->instance(GitHub::class, $github);
}
also tried:
use GrahamCampbell\GitHub\Facades\GitHub;
public function testExample()
{
Github::shouldReceive('api')->once()->andReturn(['id' => 1]);
$repo = Github::repo()->show('symfony', 'demo');
dd($repo);
}
Returns: Mockery\Exception\BadMethodCallException: Method Mockery_0::repo() does not exist on this mock object
Just to confirm, if I remove the GitHub::shouldReceive... line, it's successful but actually hits the GitHub API.
With the last example you are almost there. Remember you are trying to mock a two step call, first a static method and a call to an instance, therefor the mock should emulate that.
Create the repository that the repo() call will return. Using standard mockery functionality.
use Github\Api\Repo;
$repoMock = Mockery::mock(Repo::class);
$repoMock->shouldReceive('show')->with('symfony', 'demo')->once()->andReturn(['id' => 1]);
Now you can set the return type of the repo call through Laravels approach to mock facades.
Github::shouldReceive('repo')->once()->andReturn($repoMock);
When you call your code repo will return the repo mock, that expects a show call with the parameters symfony and demo.
$repo = Github::repo()->show('symfony', 'demo');
First off, I am building using Symfony components. I am using 3.4. I was following the form tutorial https://symfony.com/doc/3.4/components/form.html which lead me to this page
https://symfony.com/doc/current/forms.html#usage
I noticed that Symfony added a Form directory to my application.
This was great! I thought I was on my way. So, in my controller, I added this line.
$form = Forms::createFormFactory();
When I tried loading the page, everything went well with no error messages until I added the next two lines.
->addExtension(new HttpFoundationExtension())
->getForm();
I removed the ->addExtension(new HttpFoundationExtension()) line and left the ->getForm() thinking it would process without the add method call. It did not. So, I backed up to see if the IDE would type hint for me.
In the IDE PHPStorm, these are the methods that I have access to but not getForm per the tutorial
Every tutorial I have tried ends with not being able to find some method that does not exist. What do I need to install in order to have access to the ->getForm() method?
UPDATE:
I have made a couple of steps forward.
$form = Forms::createFormFactory()
->createBuilder(TaskType::class);
The code above loads with no errors. (Why is still fuzzy). But next stop is the createView(). None existant also. I only get hinted with create().
Reading between the lines in this video help with the last two steps. https://symfonycasts.com/screencast/symfony3-forms/render-form-bootstrap#play
UPDATE 2:
This is what I have now.
$session = new Session();
$csrfManager = new CsrfTokenManager();
$help = new \Twig_ExtensionInterface();
$formFactory = Forms::createFormFactoryBuilder()
->getFormFactory();
$form = $formFactory->createBuilder(TaskType::class)
->getForm();
//$form->handleRequest();
$loader = new FilesystemLoader('../../templates/billing');
$twig = new Environment($loader, [
'debug' => true,
]);
$twig->addExtension(new HeaderExtension());
$twig->addExtension(new DebugExtension());
$twig->addExtension($help, FormRendererEngineInterface::class);
return $twig->render('requeueCharge.html.twig', [
'payments' => 'Charge',
'reportForm' => $form->createView()
]);
Does anyone know of an update standalone for example? The one that everyone keeps pointing two is 6 years old. There have been many things deprecated in that time period. So, it is not an example to follow.
Your Form class and method createFormFactory must return object that implement FormBuilderInterface then getForm method will be available. You need create formBuilder object.
But this can't be called from static method because formBuilder object need dependency from DI container. Look at controller.
If you want you need register your own class in DI and create formBuilder object with dependencies and return that instance of object.
EDIT
You don't need to use abstract controller. You can create your own class which is registered in DI for geting dependencies. In that class you create method which create new FormBuilder(..dependencies from DI from your class ...) and return instance of that FormBuilder. Then you can inject your class in controller via DI.
Example (not tested)
// class registered in DI
class CustomFormFactory
{
private $_factory;
private $_dispatcher;
public CustomFormFactory(EventDispatcherInterface $dispatcher, FormFactoryInterface $factory)
{
$_dispatcher = $dispatcher;
$_factory = $factory;
}
public function createForm(?string $name, ?string $dataClass, array $options = []): FormBuilderInterface
{
// create instance in combination with DI dependencies (factory..) and your parameters
return new FormBuilder($name, $dataClass, $_dispatcher, $_factory, $options);
}
}
Usage
$factory = $this->container->get('CustomFormFactory');
$fb = $factory->createForm();
$form = $fb->getForm();
I'm trying to create unit tests to test some specific classes. I use app()->make() to instantiate the classes to test. So actually, no HTTP requests are needed.
However, some of the tested functions need information from the routing parameters so they'll make calls e.g. request()->route()->parameter('info'), and this throws an exception:
Call to a member function parameter() on null.
I've played around a lot and tried something like:
request()->attributes = new \Symfony\Component\HttpFoundation\ParameterBag(['info' => 5]);
request()->route(['info' => 5]);
request()->initialize([], [], ['info' => 5], [], [], [], null);
but none of them worked...
How could I manually initialize the router and feed some routing parameters to it? Or simply make request()->route()->parameter() available?
Update
#Loek: You didn't understand me. Basically, I'm doing:
class SomeTest extends TestCase
{
public function test_info()
{
$info = request()->route()->parameter('info');
$this->assertEquals($info, 'hello_world');
}
}
No "requests" involved. The request()->route()->parameter() call is actually located in a service provider in my real code. This test case is specifically used to test that service provider. There isn't a route which will print the returning value from the methods in that provider.
I assume you need to simulate a request without actually dispatching it. With a simulated request in place, you want to probe it for parameter values and develop your testcase.
There's an undocumented way to do this. You'll be surprised!
The problem
As you already know, Laravel's Illuminate\Http\Request class builds upon Symfony\Component\HttpFoundation\Request. The upstream class does not allow you to setup a request URI manually in a setRequestUri() way. It figures it out based on the actual request headers. No other way around.
OK, enough with the chatter. Let's try to simulate a request:
<?php
use Illuminate\Http\Request;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$request = new Request([], [], ['info' => 5]);
dd($request->route()->parameter('info'));
}
}
As you mentioned yourself, you'll get a:
Error: Call to a member function parameter() on null
We need a Route
Why is that? Why route() returns null?
Have a look at its implementation as well as the implementation of its companion method; getRouteResolver(). The getRouteResolver() method returns an empty closure, then route() calls it and so the $route variable will be null. Then it gets returned and thus... the error.
In a real HTTP request context, Laravel sets up its route resolver, so you won't get such errors. Now that you're simulating the request, you need to set up that by yourself. Let's see how.
<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$request = new Request([], [], ['info' => 5]);
$request->setRouteResolver(function () use ($request) {
return (new Route('GET', 'testing/{info}', []))->bind($request);
});
dd($request->route()->parameter('info'));
}
}
See another example of creating Routes from Laravel's own RouteCollection class.
Empty parameters bag
So, now you won't get that error because you actually have a route with the request object bound to it. But it won't work yet. If we run phpunit at this point, we'll get a null in the face! If you do a dd($request->route()) you'll see that even though it has the info parameter name set up, its parameters array is empty:
Illuminate\Routing\Route {#250
#uri: "testing/{info}"
#methods: array:2 [
0 => "GET"
1 => "HEAD"
]
#action: array:1 [
"uses" => null
]
#controller: null
#defaults: []
#wheres: []
#parameters: [] <===================== HERE
#parameterNames: array:1 [
0 => "info"
]
#compiled: Symfony\Component\Routing\CompiledRoute {#252
-variables: array:1 [
0 => "info"
]
-tokens: array:2 [
0 => array:4 [
0 => "variable"
1 => "/"
2 => "[^/]++"
3 => "info"
]
1 => array:2 [
0 => "text"
1 => "/testing"
]
]
-staticPrefix: "/testing"
-regex: "#^/testing/(?P<info>[^/]++)$#s"
-pathVariables: array:1 [
0 => "info"
]
-hostVariables: []
-hostRegex: null
-hostTokens: []
}
#router: null
#container: null
}
So passing that ['info' => 5] to Request constructor has no effect whatsoever. Let's have a look at the Route class and see how its $parameters property is getting populated.
When we bind the request object to the route, the $parameters property gets populated by a subsequent call to the bindParameters() method which in turn calls bindPathParameters() to figure out path-specific parameters (we don't have a host parameter in this case).
That method matches request's decoded path against a regex of Symfony's Symfony\Component\Routing\CompiledRoute (You can see that regex in the above dump as well) and returns the matches which are path parameters. It will be empty if the path doesn't match the pattern (which is our case).
/**
* Get the parameter matches for the path portion of the URI.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function bindPathParameters(Request $request)
{
preg_match($this->compiled->getRegex(), '/'.$request->decodedPath(), $matches);
return $matches;
}
The problem is that when there's no actual request, that $request->decodedPath() returns / which does not match the pattern. So the parameters bag will be empty, no matter what.
Spoofing the request URI
If you follow that decodedPath() method on the Request class, you'll go deep through a couple of methods which will finally return a value from prepareRequestUri() of Symfony\Component\HttpFoundation\Request. There, exactly in that method, you'll find the answer to your question.
It's figuring out the request URI by probing a bunch of HTTP headers. It first checks for X_ORIGINAL_URL, then X_REWRITE_URL, then a few others and finally for the REQUEST_URI header. You can set either of these headers to actually spoof the request URI and achieve minimum simulation of a http request. Let's see.
<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$request = new Request([], [], [], [], [], ['REQUEST_URI' => 'testing/5']);
$request->setRouteResolver(function () use ($request) {
return (new Route('GET', 'testing/{info}', []))->bind($request);
});
dd($request->route()->parameter('info'));
}
}
To your surprise, it prints out 5; the value of info parameter.
Cleanup
You might want to extract the functionality to a helper simulateRequest() method, or a SimulatesRequests trait which can be used across your test cases.
Mocking
Even if it was absolutely impossible to spoof the request URI like the approach above, you could partially mock the request class and set your expected request URI. Something along the lines of:
<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
class ExampleTest extends TestCase
{
public function testBasicExample()
{
$requestMock = Mockery::mock(Request::class)
->makePartial()
->shouldReceive('path')
->once()
->andReturn('testing/5');
app()->instance('request', $requestMock->getMock());
$request = request();
$request->setRouteResolver(function () use ($request) {
return (new Route('GET', 'testing/{info}', []))->bind($request);
});
dd($request->route()->parameter('info'));
}
}
This prints out 5 as well.
I ran into this problem today using Laravel7 here is how I solved it, hope it helps somebody
I'm writing unit tests for a middleware, it needs to check for some route parameters, so what I'm doing is creating a fixed request to pass it to the middleware
$request = Request::create('/api/company/{company}', 'GET');
$request->setRouteResolver(function() use ($company) {
$stub = $this->createStub(Route::class);
$stub->expects($this->any())->method('hasParameter')->with('company')->willReturn(true);
$stub->expects($this->any())->method('parameter')->with('company')->willReturn($company->id); // not $adminUser's company
return $stub;
});
Since route is implemented as a closure, you can access a route parameter directly in the route, without explicitly calling parameter('info'). These two calls returns the same:
$info = $request->route()->parameter('info');
$info = $request->route('info');
The second way, makes mocking the 'info' parameter very easy:
$request = $this->createMock(Request::class);
$request->expects($this->once())->method('route')->willReturn('HelloWorld');
$info = $request->route('info');
$this->assertEquals($info, 'HelloWorld');
Of course to exploit this method in your tests, you should inject the Request object in your class under test, instead of using the Laravel global request object through the request() method.
Using the Laravel phpunit wrapper, you can let your test class extend TestCase and use the visit() function.
If you want to be stricter (which in unit testing is probably a good thing), this method isn't really recommended.
class UserTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function testExample()
{
// This is readable but there's a lot of under-the-hood magic
$this->visit('/home')
->see('Welcome')
->seePageIs('/home');
// You can still be explicit and use phpunit functions
$this->assertTrue(true);
}
}