How to mock Cache::remember in Laravel - php

Within a unit test method, I tried to mock a Cache::remember response like this:
Cache::shouldReceive('remember')
->once()
->with('my_key', 120, function() {}) // There are 3 args in remember method
->andReturn([]);
But I get this error:
exception 'Mockery\Exception\NoMatchingExpectationException'
with message 'No matching handler found for
Mockery_0_Illuminate_Cache_CacheManager::remember
("my_key", 120, object(Closure)). Either the method was
unexpected or its arguments matched no expected argument
list for this method
I don't understand why I got this error and did not found anything in Laravel documentation about this. It says there is no matching, but it seems to match.
How can I mock a Cache::remember response?

replace 3rd argument of with method to \Closure::class to matching any closures.
Cache::shouldReceive('remember')
->once()
->with('my_key', 120, \Closure::class)
->andReturn([]);

See this issue about challenges with mocking the Cache facade -
https://github.com/laravel/framework/issues/10803
also reference
https://github.com/laravel/framework/issues/9729

Related

Throwing an instance of RequestException while Mocking in Laravel

I wanna mock an api with success and failed results, while the request failing will throw RequestException but I cannot use It cause it's asking for arguments like response and stuff but other Exceptions like ConnectionException will happen without needing additional parameters. any thoughts?
$wrongOtp = 12345;
$this->instance(Some::class, Mockery::mock(Some::class, function (Mockery\MockInterface $mock) use ($wrongOtp) {
$mock->shouldReceive('getProfile')->with($this->ssn, $wrongOtp, $this->track_code)
->andThrow(RequestException::class)->once();
}));
so If I replace RequestException with ConnectionException in my code above it will run the test without problem but if I use RequestException it asks for a response as first argument and I actually don't know which type of response should I provide for it.
Thanks in advance for all the help

Symfony 4: Getting additional exception data to the template

I am working on an Symfony + FOSRestBundle based REST API. I have implemented (extended) custom HttpException class which is being used in case of validation errors. Along the error message I always pass an array with detailed data about validation problems which is generated by validator class. That is basically the reason why I extended base HttpException. My idea is to show array of errors from exception to the end user of API, but with the core functionality of Symfony I always get "400 Bad request" error in JSON format (no possibility to show another key/value pair along status code and message). I started looking how could I overcome this problem and tried various principles (normalizers, listeners, ...) which I found after searching the web, but none of those really solved the problem of showing the array of errors along code and message.
I'm using Twig for generating responses (I love detailed HTML errors in dev mode, so there's no way for me to replace it with something else). I checked how HTTP kernel processes the request when an exception is thrown and found out that everything goes via exception listener to the exception controller. I created custom exception controller and tried to get the array of errors from the exception. However, the real exception is wrapped into FlattenException which discards that array. The main problem here is actually FlattenException object, because I can't get the data from it about the errors (I know why flatten exception is used there, but in my case is not really useful since I have the real array, not some active object instead of it).
I would like to somehow replace FlattenException with my custom class - I want extend it just to have another property for array of messages that would be preserved until exception controller's showAction and rendering of view template. Then I would change static create function with additional logic for handling cases when my custom type of exceptions is being wrapped. I'm not sure if that's even possible without changing the core code of Symfony and if possible I'd like to stay away from any hacks.
Another thing about which I was thinking is to extend/replace ExceptionListener. There I would just change existing logic to replace FlattenException with MyCustomFlattenException which would then get passed to my ExceptionController via showAction method. I already tried that and overrode both twig.exception_listener and http_exception_listener services (separately - once the first and in the other case the second; I did that in services.yaml), but didn't have much luck with it.
For the twig.exception_listener this is what is in my service ...
twig.exception_listener:
class: App\EventListener\ApiExceptionListener
arguments:
$controller: "%twig.exception_listener.controller%"
$logger: "logger"
tags:
- name: kernel.event_subscriber
- name: monolog.logger
channel: request
... and I always get the this error:
Fatal error: Uncaught Symfony\Component\DependencyInjection\Exception\RuntimeException: Cannot autowire service "App\EventListener\ApiExceptionListener": argument "$controller" of method "Symfony\Component\HttpKernel\EventListener\ExceptionListener::__construct()" has no type-hint, you should configure its value explicitly. in /project/vendor/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php on line 54
While for http_exception_listener this is what's in my service ...
http_exception_listener:
class: App\EventListener\ApiExceptionListener
arguments:
[
null,
null,
"%kernel.debug%",
"%kernel.charset%",
"%debug.file_link_format%",
]
tags:
- name: kernel.event_listener
event: kernel.exception
method: onKernelException
priority: -1024
- name: kernel.reset
method: reset
... and there is no any error, but still the core ExceptionListener is called instead of the one I specified in services.
This is the ApiExceptionListener class where I have some breakpoints in my IDE to check if it was really called:
<?php
namespace App\EventListener;
use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class ApiExceptionListener extends ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$test = 'ok';
}
protected function duplicateRequest(\Exception $exception, Request $request)
{
parent::duplicateRequest($exception, $request);
}
}
I would really appreciate any help or idea to get in the right direction of solution for this problem. Thank you!

Symfony: type error when using flash bag in controller

In order to get the flash bag of the session and add a flash message, in the controller I call:
$request->getSession()->getFlashBag()->addFlash(...);
(where $request is an instance of Request)
but I get the following IDE type error:
Method 'getFlashBag' not found in
null|\Symfony\Component\HttpFoundation\Session\SessionInterface
The problem is that $request->getSession() returns a SessionInterface, which does not contain the getFlashBag method.
That's why the IDE is complaining, even if the actual object returned by that method is an instance of the Session class which has the getFlashBag method.
When inside a controller, a quick solution can just be using:
$this->addFlash(...);
instead of:
$request->getSession()->getFlashBag()->addFlash(...);

Testing Exceptions in PHPUnit

I am new to unit testing and now trying to write some test cases using PHPUnit for a Laravel application. I have done one basic test which is like the following :
public function testGetPointsByApp()
{
$this
->json(
'GET',
self::URL.'/'.$value
)
->assertResponseStatus(200)
->seeJson([
'status' => 'success',
])
->seeJsonStructure([
'status',
'message',
'data' => []
]);
}
Now this is working. But I would like to know how can I test for invalid inputs and for the cases like there is not data at all with the given parameters. I am throwing corresponding exceptions in each of these cases and it will add particular messages. How can I test whether it is thrown ?
I don't want to include it to this method and would like to write some other methods to test different scenarios in the API like invalid UUID, no data etc. So for each time, I need to call the API url or this call can be done from any kind of set up function ?
I don't know laravel but in PHPUNIT you can test if exception is thrown with expectException function. For example for InvalidArgumentException
$this->expectException(InvalidArgumentException::class);
You can also use annotation #expectedException and there are also a lot of things that can be tested in exception like expectExceptionCode() etc.
More you can find here
Since you're not doing an Unit test (this kind of test you're doing is an acceptance test), you cannot check for the exception it self, you will look for a bad response:
$this
->json(
'GET',
self::URL.'/'.$BAD_VALUE
)
->assertResponseStatus(422)
->seeJson([
/* Whatever you want to receive at invalid request */
])
->seeJsonStructure([
/* Whatever you want to receive at invalid request */
]);
Laravel validation errors returns 422 - unprocessable entity HTTP code.
You can check the response also for your expected error messages or something else.

PHPunit: How to mock a method that has a parameter AND a returned value

Using PHPUnit, I wonder if we can mock an object to test if a method is called with an expected parameter, and a returned value?
In the doc, there are examples with passing parameter, or returned value, but not both...
I tried using this:
// My object to test
$hoard = new Hoard();
// Mock objects used as parameters
$item = $this->getMock('Item');
$user = $this->getMock('User', array('removeItem'));
...
$user->expects($this->once())
->method('removeItem')
->with($this->equalTo($item));
$this->assertTrue($hoard->removeItemFromUser($item, $user));
My assertion fails because Hoard::removeItemFromUser() should return the returned value of User::removeItem(), which is true.
$user->expects($this->once())
->method('removeItem')
->with($this->equalTo($item), $this->returnValue(true));
$this->assertTrue($hoard->removeItemFromUser($item, $user));
Also fails with the following message: "Parameter count for invocation User::removeItem(Mock_Item_767aa2db Object (...)) is too low."
$user->expects($this->once())
->method('removeItem')
->with($this->equalTo($item))
->with($this->returnValue(true));
$this->assertTrue($hoard->removeItemFromUser($item, $user));
Also fails with the following message: "PHPUnit_Framework_Exception: Parameter matcher is already defined, cannot redefine"
What should I do to test this method correctly.
You need to use will instead of with for returnValue and friends.
$user->expects($this->once())
->method('removeItem')
->with($item) // equalTo() is the default; save some keystrokes
->will($this->returnValue(true)); // <-- will instead of with
$this->assertTrue($hoard->removeItemFromUser($item, $user));
I know that it's an old post, but it's on the top in searching for PHPUnit warning Method name matcher is already defined, cannot redefine, so I'll answer too.
There is other reason for such warning message. If you describe a mock behaviour in chaining method like this:
$research = $this->createMock(Research::class);
$research->expects($this->any())
->method('getId')
->willReturn(1)
->method('getAgent')
->willReturn(1);
You will get warning Method name matcher is already defined, cannot redefine. Just split it in separate statements and the warning will go away (tested on PHPUnit 7.5 and 8.3).
$research = $this->createMock(Research::class);
$research->expects($this->any())
->method('getId')
->willReturn(1);
$research->expects($this->any())
->method('getAgent')
->willReturn(1);

Categories