Mock interface as argument in phpUnit - phpunit

I have the following code, while testing zend application using phpUnit:
Tested method:
public function save(AbstractEntity $row, $request = null)
{
if ($row->getDef()) {
$this->getRepository()->clearDef();
}
$this->getRepository()->save($row);
}
So I mocked Service itself and try to test call:
public function testEditActionPostFormValid()
{
$this->mockService();
$this->mockForm();
$this->dispatch('/reference/metal/edit/1', 'POST');
$this->assertResponseStatusCode(302);
$this->assertRedirectTo('/reference/metal');
$this->assertActionName('edit');
$this->assertStandardParameters();
}
Form mocking:
private function mockForm($isValid = true)
{
$mockCategoryForm = $this->getMockBuilder(MetalForm::class)->disableOriginalConstructor()->getMock();
$mockCategoryForm->expects($this->any())->method('isValid')->willReturn($isValid);
$this->overrideService(MetalForm::class, $mockCategoryForm);
}
Service mocking:
private function mockService($findAllReturn = [])
{
$mockEntity = new Metal();
$mockEntity->setName('testName');
$mockService = $this->getMockBuilder(MetalService::class)->disableOriginalConstructor()->getMock();
$mockService->expects($this->any())->method('find')->willReturn($mockEntity);
$mockService->expects($this->any())->method('findAll')->willReturn($findAllReturn);
$this->overrideService(MetalService::class, $mockService);
}
But I get the following error:
Failed asserting response code "302", actual status code is "500"
Exceptions raised:
Exception 'TypeError' with message 'Argument 1 passed to Mock_MetalService_66253896::save() must implement interface Core\Entity\AbstractEntity, null given, called in /opt/project/module/Reference/src/Controller/PlainReferenceController.php on line 51' in /opt/project/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php(646) : eval()'d code:35
If I delete interface as the first argument test will path well. How to mock AbstractEntity interface here?

Related

Symfony Messenger with RabbitMQ JsonSerialize problem with requeue

I have created 2 queues in Rabbit. First if main, second should be used when Exception is thrown in consumer. On older Symfony with ampq bundle is all ok, but in 6.2 with Messenger not. There is problem with JsonSerializer. I "copy/paste" solution recomended by manual of Symfony.
Lets have first queue named queue with this parameters (second is queue_retry, but it doesnt matter at this time):
arguments:
x-dead-letter-exchange: 'queue_retry'
x-dead-letter-routing-key: ''
And class for JsonSerializer
class RabbitMessageMessengerJsonSerializer implements SerializerInterface
{
public function decode(array $encodedEnvelope): Envelope
{
$body = $encodedEnvelope['body'];
$headers = $encodedEnvelope['headers'];
$data = json_decode($body, true);
$message = new RabbitMessageMessenger($data);
$envelope = new Envelope($message);
$stamps = [];
if (isset($headers['stamps'])) {
$stamps = unserialize($headers['stamps']);
}
return $envelope->with(... $stamps);
}
public function encode(Envelope $envelope): array
{
$message = $envelope->getMessage();
if ($message instanceof RabbitMessageMessenger) {
$data = $message->getContent();
} else {
throw new \Exception('Unsupported message class');
}
$allStamps = [];
foreach ($envelope->all() as $stamps) {
$allStamps = array_merge($allStamps, $stamps);
}
return [
'body' => json_encode($data),
'headers' => [
'stamps' => serialize($allStamps),
],
];
}
}
When I run consumer and throw Exception, I got this message:
In RabbitMessageMessengerJsonSerializer.php line 46:
[Exception] Serialization of 'Closure' is not allowed
In this case is message "copied" and exists in both queues. I need to take it from "queue" and when it fails "move" to "queue_retry". And without problems.
If I remove stamps (set to empty array) message is copied into "queue" many times until I brak running of consumer.
What is wrong?
Thank you very much

Unable to get mock container to return anything but null

I have a test that fails due to me being unable to successfully stub the get method of the Controller:
1) Tests\my-project\BackendBundle\Service\PdfGenerateServiceTest::test_getHttpPathToPtImage_should_return_the_default_avatar_when_photo_is_null
TypeError: Argument 1 passed to Mock_Pdf_d0288d34::setLogger() must implement interface Psr\Log\LoggerInterface, null given, called in /var/www/my-project/src/my-project/BackendBundle/Service/PdfGenerateService.php on line 66
The test
public function test_getHttpPathToPtImage_should_return_the_default_avatar_when_photo_is_null()
{
$protocolAndHost = "http://foo.bar.com";
$service = new PdfGenerateService($this->createFileServiceMock(), $this->createTranslatorMock(), $this->createSnappyMock(), $this->createContainerMock(), $protocolAndHost);
$httpPathToPtImage = $service->getHttpPathToPtImage(null);
self::assertEquals($httpPathToPtImage, $protocolAndHost . "abc/def");
}
The constructor that is failing
public function __construct(FileService $fileService, Translator $translator, Pdf $snappy, ContainerInterface $container, string $protocolAndHost)
{
$this->fileService = $fileService;
$this->translator = $translator;
$this->currentLocale = $this->translator->getLocale();
/* Could reconfigure the service using `service.yml` to pass these in using DI */
$this->twig = $container->get('twig');
$this->logger = $container->get('logger'); // <--- should not be null
$timeoutInSeconds = 15; // can be high, since the job is done async in a job (user does not wait)
$snappy->setLogger($this->logger); // <--- THIS FAILS due to $this->logger being null
The stubs
protected function createContainerMock()
{
$containerMock = $this->createMock('Symfony\Component\DependencyInjection\ContainerInterface');
$loggerMock = $this->createLoggerMock();
$containerMock->method('get')->will($this->returnValueMap([
['logger', $loggerMock]
]));
return $containerMock;
}
I do not really understand why the the get('logger') call just returns null when I have setup a mock to be returned using the returnValueMap call above.
By chance, I just found a SO question on this topic where someone mentioned that you need to supply all parameters, even optional ones. I then checked out the interface, which does indeed list a second parameter:
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE);
Still, changing the map to ['logger', null, $loggerMock] made no change, so I am a bit at a loss as to what to try next.
Phpunit 6.5, PHP 7.2, Symfony 3.4
You were really close to the solution. When providing a value for an optional parameter in returnValueMap, you must use the value itself, not just null.
So instead of
['logger', null, $loggerMock]
try specifying
['logger', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $loggerMock]
Complete call looks like this:
$containerMock->method('get')->will($this->returnValueMap([
['logger', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $loggerMock]
]));

Testing console command that uses kernel.terminate event (KernelTestCase)

I want to test a symfony 4 console command.
Importantly, I want to test the situation after all kernel.terminate eventlisteners have finished. To demonstrate here, I have a most simple listener that var_dumps a string:
class NotificationCenter implements EventSubscriberInterface
{
public function onException()
{
$this->wasExceptionThrown = true;
}
public function onTerminate()
{
if ($this->wasExceptionThrown) {
return;
}
var_dump("Hiho from terminate");
}
public static function getSubscribedEvents()
{
$listeners = [
KernelEvents::EXCEPTION => ['onException', 1024],
KernelEvents::TERMINATE => ['onTerminate', 1024],
];
if (class_exists('Symfony\Component\Console\ConsoleEvents')) {
$listeners[class_exists('Symfony\Component\Console\Event\ConsoleErrorEvent') ? ConsoleEvents::ERROR : ConsoleEvents::EXCEPTION] = ['onException', 1024];
$listeners[ConsoleEvents::TERMINATE] = ['onTerminate', 1024];
}
return $listeners;
}
public function reset()
{
$this->wasExceptionThrown = false;
}
}
So, with this eventlistener enabled, any call to any command (or http route) outputs this string.
According to the docs, I have created a Test case:
class MissingDataNotifyCommandTest extends KernelTestCase
{
protected function setUp()
{
self::bootKernel();
}
public function testHiHoIsThere()
{
$application = new Application(static::$kernel);
$command = $application->find('debug:event-dispatcher');
$commandTester = new CommandTester($command);
$commandTester->execute(['command' => $command->getName()]);
$this->assertContains('Hiho from terminate', $commandTester->getDisplay());
}
}
But the assertion fails. Funny enough, the debug command that var_dumps is listed under kernel.terminate.
How can I make sure the kernel.terminate eventlisteners are dispatched during a KernelTestCase?
Edit: my solution
Ok, with Tomas Votruba's help, I was able to find a (partial) solution:
class MissingDataNotifyCommandTest extends KernelTestCase
{
protected function setUp()
{
self::bootKernel();
}
public function testHiHoIsThere()
{
$application = new Application(static::$kernel);
$application->setAutoExit(false);
$application->setCatchExceptions(false);
$stringInput = ['debug:event-dispatcher'];
$input = new StringInput(implode(' ', $stringInput));
$application->run($input, new NullOutput());
}
}
The only problem I could not solve was to get the output of the command back into my code. Moreover, this somehow does run some framework/container code twice (once when booting the kernel inside the tests, once when executing the command).
I double checked and it looks like you're testing Console, not Kernel. They're 2 different classes.
Kernel terminate event is invoked here.
Console Application "terminate" event is invoked here.
Try adding console terminate event to subscriber - console.terminate.
class YourSubscriber implements EventSubccriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::TERMINATE => 'someFunction',
ConsoleEvents::TERMINATE => 'someFunction',
];
}
public function someFunction()
{
}
}

Symfony2 set class variable with init or construct methods

Have recently been using Symfony2 after using ZF for some time.
I am having problems trying to do something relatively simple, I think.
The following code is within a controller:
private $current_setid = "";
public function __construct() {
$current_set = $this->getCurrentSet();
if ($current_set == "") {
return $this->redirect($this->generateUrl('selectset'));
}
$this->current_setid = $current_set;
}
public function getCurrentSet() {
$session = $this->get("session");
$set = $session->get('set');
return $set;
}
public function setCurrentSet($setid) {
$session = $this->get("session");
$session->set('set', "$setid");
}
If I use __construct() I get errors like:
Fatal error: Call to a member function get() on a non-object in
I have tried using __init() and init() both of which do not seem to get called.
Can anyone point me in the right direction? Is there a simple way to do this or do I have to look into event listeners?
Have you tried getting your session like they do in official documentation?
$session = $this->getRequest()->getSession();
$foo = $session->get('foo');
Basically get fetch dependencies from container and container in the Controller is injected using setter dependency injection. You just not have container in the time of __construct yet.
Just ended up opting for placing a check in every method in the class. Seems silly to have to do that but I find I often have to do that in Symfony2 with the lack of init, postDispatch type methods like ZF has.
Even trying to remove the check to another method was counter productive as I still had to check the return from that method as $this->redirect does not seem to work unless it is within an Action method. For example:
public function isSetSet() {
$current_set = $this->getCurrentSet();
if ($current_set == "") {
$url = $this->generateUrl('selectset');
return $this->redirect($url);
}
return TRUE;
}
public function someAction() {
$check = $this->isSetSet();
if($check != TRUE){
return $check;
}
...
}
So each method needs that 4 line check but the whole check can be done in 4 lines anyway so no need for that extra method:
public function anotherAction() {
$current_setid = $this->getCurrentSet();
if ($current_setid == "") {
return $this->redirect($this->generateUrl('selectset'));
}
...
}

Dynamically implement interface in Groovy using invokeMethod

Groovy offers some really neat language features for dealing with and implementing Java interfaces, but I seem kind of stuck.
I want to dynamically implement an Interface on a Groovy class and intercept all method calls on that interface using GroovyInterceptable.invokeMethod. Here what I tried so far:
public interface TestInterface
{
public void doBla();
public String hello(String world);
}
import groovy.lang.GroovyInterceptable;
class GormInterfaceDispatcher implements GroovyInterceptable
{
def invokeMethod(String name, args) {
System.out.println ("Beginning $name with $args")
def metaMethod = metaClass.getMetaMethod(name, args)
def result = null
if(!metaMethod)
{
// Do something cool here with the method call
}
else
result = metaMethod.invoke(this, args)
System.out.println ("Completed $name")
return result
}
TestInterface getFromClosure()
{
// This works, but how do I get the method name from here?
// I find that even more elegant than using invokeMethod
return { Object[] args -> System.out.println "An unknown method called with $args" }.asType(TestInterface.class)
}
TestInterface getThisAsInterface()
{
// I'm using asType because I won't know the interfaces
// This returns null
return this.asType(TestInterface.class)
}
public static void main(String[] args)
{
def gid = new GormInterfaceDispatcher()
TestInterface ti = gid.getFromClosure()
assert ti != null
ti.doBla() // Works
TestInterface ti2 = gid.getThisAsInterface()
assert ti2 != null // Assertion failed
ti2.doBla()
}
}
Returning the Closure works fine, but I couldn't figure a way to find out the name of the method being called there.
Trying to make a Proxy to the this reference itself (so that method calls will call invokeMethod) returns null.
You could use the Map coercion feature of Groovy to dynamically generate a Map that represents the given interface:
TestInterface getMapAsInterface() {
def map = [:]
TestInterface.class.methods.each() { method ->
map."$method.name" = { Object[] args->
println "Called method ${method.name} with ${args}"
}
}
return map.asType(TestInterface.class)
}
To complete the response of Christoph, as stated by this page, you can implement an interface with a closure. For example:
def map = [doBla: { println 'Bla!'}, hello: {world -> "Hello $world".toString()}] as TestInterface
map.hello 'Groovy' // returns 'Hello Groovy'

Resources