(laravel) supply mocked data to view when testing? - php

Suppose I have a blade template:
// resources/views/fragments/foo.blade.php
<p>{{ $foo }}</p>
(This is obviously a very stripped-down version of my question. I can't see why I'd ever need to test a blade template that simple.) In my controller, I would normally populate that template like this:
// app/Http/Controllers/FooController.php
$parameters = ['foo' => ''];
return view('fragments.foo', $parameters);
The problem is I'd like to write a phpunit test to ensure that template renders successfully when I pass it a specific value for $foo, but I can't figure out how to send data to the view when running the unit test. I tried this:
// tests/TestFooController.php
$this->withSession(['foo' => 'bar'])->visit('/foo')->assertViewHas('foo', 'bar');
but that test failed. How can I pass my own data to the view from within my test?

Finally googled around enough to get a workaround:
// tests/TestFooController.php
$parameters = ['foo' => 'bar'];
view()->composer('fragments.foo', function ($view) use ($parameters) {
$view->with($parameters);
});
$this->visit('/foo')
->assertViewHasAll([
'foo' => 'bar',
]);
The cool thing (depending on how much independence you like when testing) is that if my FooController passes other variables, but I only override one of them in the test's view composer, the other non-overridden values will still be there.

Related

How to test laravel controller method?

My controller looks like this:
public function store(Request $request) {
$validateData = $request->validate([
'name' => 'required|unique:languages',
'code' => 'required|size:3',
'flag' => 'required|size:2'
]);
$language = new Language();
$language->name = $request->name;
$language->code = $request->code;
$language->flag = $request->flag;
$saveLanguage = $language->save();
if(!$saveLanguage){
return response()->json(['error'=>'Something went wrong, please try later.'],500);
}
return response()->json(['success'=>'Language has been created successfully', 'data'=>$language],200);
As you can see, I am instantiating a new Language object and everything works fine, but first problem is, imagine I change Language class in future (for example: you have to pass 2 parameters in constructor), I have to change this controller and every other controllers where I am instantiating Language object.
The second problem is I can't or it's too hard to test this controller.
I am curious what is the best solution to solve this problems in laravel?
For example is it a good solution to use simple factory or factory method pattern for every model I am using in my controllers.
I think when you write something like this $var = new SomeClass() in other class, this otherClass is depends on SomeClass and when you want to change SomeClass you have to update otherClass to. What do you think abaout this, how can I avoid this.
Or you could use the Eloquent Create function, this way you don't have to worry about the constructor.
$language = Language::create($request->only(['name', 'code', 'flag']));
This function will insert the data in the database and return the model.
Problem #1: If you change the signature of a constructor (e.g. adding new required parameters to the Language class constructor), then yes, you'll need to update all places where that constructor is called. There's no way around that, unless you can encapsulate the logic for what those parameters should be into a helper method somewhere (though you'll still need to update all calls the first time, while future changes will be abstracted away). However, modern IDEs (e.g. PHPStorm) can help you automate the process of replacing the old signature with the new signature.
Problem #2: You can actually test a controller like this quite easily. To take an example from the Laravel docs and apply it to your code, you could do something like this (in Laravel 7.x):
$response = $this->postJson('/language', ['name' => 'Swedish', 'code' => 'swe', 'flag => 'SE']);
$response
->assertStatus(200)
->assertJson([
'success' => 'Language has been created successfully',
])
->assertJsonPath('data.name', 'Swedish')
->assertJsonPath('data.code', 'swe')
->assertJsonPath('data.flag', 'SE');

Passing object to partial() view helper in ZF2

I'm trying to pass an object to the partial() view helper. While working with normal variables workes fine, I'm not able to pass object to this function.
For example, this workes fine when using $this->test in partial:
$this->partial("module/folder/partial.phtml", array(
"test" => "foo",
));
But doing the same with an object will result in nothing in partial:
$this->partial("module/folder/partial.phtml", array(
"test" => $this,
));
I've even tried using setObjectKey, what didn't woked too:
$this->partial()->setObjectKey("test");
$this->partial("module/folder/partial.phtml", $this);
Is there a way to pass an object to an single partial in ZF2?
I want to do this, because my view actually contains many informations the partial needs. I could add them all line-by-line to the partial, but that would just be overhead ...
When you pass the parent Zend\View\Renderer\PhpRenderer to your partial it will overwrite/replaces the $test variable with the Zend\View\Renderer\PhpRenderer of your partial, like it is re-using the class. So resulting in a object that is empty as your partial PhpRenderer has no other variables included.
If you want the variables from the parent PhpRender use the following:
$this->partial('folder/partial', ['vars' => $this->vars()->getArrayCopy()]);
// Or so, so you don't need to store the originals within the vars key
$this->partial('folder/partial', $this->vars()->getArrayCopy());
Now within your partial.phtml:
$this->vars['foo']
To confirm this, let your controller return some variable ['foo' => 'bar'] or setup a ViewModel with some test variables. Now setup your partial:
$this->partial('folder/partial.phtml', ['test' => 'abc' 'render' => $this].
And when you debug or var_dump() your PhpRenders variable you'll see that $render containts the variable $test with a value of abc and not the key 'foo' with 'bar' as its value. So it looks like the PhpRenderer is being reused so passing the parent PhpRenderer is not possible.

Laravel: call static function inside blade template

I found very strange situation. I have a collection with some results and I wanna grab for each of that results the saved in cache information. For that I have a Cache class, which has one static function get(). Unfortunately I am not receiving anything when I call it in the foreach loop of my Blade template.
#foreach($prognoses as $sport_prognose)
<?php
$pr = Cache::get(Config::get('variables.cache.prediction'), ['id' => $sport_prognose['id']]);
print_r($pr);
die();
?>
#endforeach
If I call the same function inside the Controller is display me the needed information, but not as in the example above.
Why is that ?
You can use cache() and config() and other global helpers instead of facades to avoid this kind of problem.
Inside the blade template, you can write something like this:
{{ $pc::getProducts($ship->products) }}
Notice the use of variables. Obviously getProducts is a static method inside the controller, and $ship->products is a variable coming from an array. Let's make it simple: suppose $ship->products is 1, and getProducts is this:
static function getProducts($id) { echo "id is $id; }
If you ran this script, you'd get an error because the template lacks the value of $pc. How do you get around this? You need to pass the value of $this to the template:
return View::make('shipping.index')->with(['pc' => $this, 'shipping' => $shippings);
Here shipping.index is the template, and pc is getting a value of $this, which allows $pc to get accceess to getProducts inside the blade template.

Passing data to views without "with()" in view composers

I love the way of passing data to views in Laravel. But I don't use the "with" methode, I prefer to pass all my data as the second argument in the view helper function:
$data = [
'name' => Auth::User() -> name
]
return view('dashboard', $data);
Now it's very easy to use my data in the view:
Hello {{ $name }}
There's no need to do
Hello {{ $data['name'] }}
But here is my problem:
I want to do the same in a view composer. But the only way I have seen to pass data to views with view composers is this:
public function compose(View $view)
{
$data = [
'name' => Auth::User() -> name
]
$view -> with('data', $data);
}
But this requires me to do
Hello {{ $data['name'] }}
in my view, which I don't want. I want to use the short syntax. So is there a way to pass it like I described above? As second argument of the view function?
Thanks
Just step through each part of your array with a foreach loop:
foreach($data as $key => $value) { $view->with($key, $value); }
In answer to your question about just calling the view function an passing values, the answer is no, you can't do it that way.
Think about what you're doing in your first example. When you call view('template', $data), you're calling a helper function defined in the Laravel core. What is that function doing? It's instantiating a view, probably by calling View::make()->with(). In other words, behind the scenes it's doing the thing you want to avoid.
Now how would that work with a view composer? I imagine it would go something like this:
In your controller, you call view('template', $somedata).
You now have a view object with $somedata included.
On the way to rendering the HTML page, Laravel calls your view composer, passing along the view object you created a moment ago.
Then you call view($newdata) (or something like that - I'm not clear on what the syntax would be) and it would attach the new data to the existing view object. But this is not something that the Laravel developers have done. That's not to say it couldn't be done, it is just not a use case that they considered.
What you can do is step through your new data in the view composer and add the individual values to the existing view, like this:
foreach ($newdata as $key => $value) {
$view->with($key, $value);
}

Laravel controller tests, handling redirect

I have a bit of an issue, the below code is from one of the methods within my controller that I'm testing.
The scenario is, you save a record and you're automatically directed to 'viewing' that record. So I am passing in the items id upon save to the redirect...
However, when running the tests I receive 'ErrorException: Trying to get property of non-object' if I pass in the id of the object straight off. So the work around I'm doing the pass the test is a ternary condition to see if the output is an object...surely there must be a better way of doing this?
I'm using Mockery, and have created a mock class/interface for the Projects model which is injected into the Projects main controller.
Here's the method:
public function store()
{
// Required to use Laravels 'Input' class to catch the form data
// This is because the mock tests don't pick up ordinary $_POST
$project = $this->project->create(Input::only('projects'));
if (count(Input::only('contributers')['contributers']) > 0) {
$output = Contributer::insert(Input::only('contributers')['contributers']);
}
// Checking whether the output is an object, as tests fail as the object isn't instatiated
// through the mock within the tests
return Redirect::route('projects.show', (is_object($project)?$project->id:null))
->with('fash', 'New project has been created');
}
And heres the test which is testing the redirected route.
Input::replace($input = ['title' => 'Foo Title']);
$this->mock->shouldReceive('create')->once();
$this->call('POST', 'projects');
$this->assertRedirectedToRoute('projects.show');
$this->assertSessionHas('flash');
You have to define the response of your mock when the method create is called to properly simulate the real behavior :
$mockProject = new StdClass; // or a new mock object
$mockProject->id = 1;
$this->mock->shouldReceive('create')->once()->andReturn($mockProject);

Categories