I'm pretty new to Symfony and phpspec so please feel free to criticize heavily. So the issue is that I'm constantly getting PHP Fatal error: Call to a member function write() on a non-object.
Basically the class which is tested should write output to the console. In the constructor first I'm creating stream and then passing this stream to another class which is responsible for the output in the console. This is the main code.
Class ScreenshotTaker :
<?php
namespace Bex\Behat\ScreenshotExtension\Service;
use Behat\Mink\Mink;
use Behat\Testwork\Output\Printer\StreamOutputPrinter;
use Bex\Behat\ScreenshotExtension\Driver\ImageDriverInterface;
use Behat\Testwork\Output\Printer\Factory\ConsoleOutputFactory;
/**
* This class is responsible for taking screenshot by using the Mink session
*
* #license http://opensource.org/licenses/MIT The MIT License
*/
class ScreenshotTaker
{
/** #var Mink $mink */
private $mink;
/** #var ConsoleOutputFactory $output */
private $output;
/** #var ImageDriverInterface[] $imageDrivers */
private $imageDrivers;
/** #var StreamOutputPrinter $outputStream */
private $outputStream;
/**
* Constructor
*
* #param Mink $mink
* #param ConsoleOutputFactory $output
* #param ImageDriverInterface[] $imageDrivers
*/
public function __construct(Mink $mink, ConsoleOutputFactory $output, array $imageDrivers)
{
$this->mink = $mink;
$this->output = $output;
$this->imageDrivers = $imageDrivers;
$this->outputStream = new StreamOutputPrinter ($output);
}
/**
* Save the screenshot as the given filename
*
* #param string $fileName
*/
public function takeScreenshot($fileName = 'failure.png')
{
try {
$screenshot = $this->mink->getSession()->getScreenshot();
foreach ($this->imageDrivers as $imageDriver) {
$imageUrl = $imageDriver->upload($screenshot, $fileName);
$this->outputStream->writeln('Screenshot has been taken. Open image at ' . $imageUrl);
}
} catch (\Exception $e) {
$this->outputStream->writeln($e->getMessage());
}
}
}
Now this is the phpspec test.I'm passing the ConsoleOutputFactory which is used in the constructor but I'm getting
PHP Fatal error: Call to a member function write() on a non-object in
Behat/Testwork/Output/Printer/StreamOutputPrinter.php on line 125
This write method is part of StreamOutputPrinter. Can you tell me what Im missing here?
ScreenshotTakerSpec:
<?php
namespace spec\Bex\Behat\ScreenshotExtension\Service;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Behat\Mink\Mink;
use Behat\Mink\Session;
use Behat\Testwork\Output\Printer\Factory\ConsoleOutputFactory;
use Bex\Behat\ScreenshotExtension\Driver\Local;
use Behat\Testwork\Output\Printer\StreamOutputPrinter;
/**
* Unit test of the class ScreenshotTaker
*
* #license http://opensource.org/licenses/MIT The MIT License
*/
class ScreenshotTakerSpec extends ObjectBehavior
{
function let(Mink $mink, ConsoleOutputFactory $output, Local $localImageDriver)
{
$this->beConstructedWith($mink, $output, [$localImageDriver]);
}
function it_is_initializable()
{
$this->shouldHaveType('Bex\Behat\ScreenshotExtension\Service\ScreenshotTaker');
}
function it_should_call_the_image_upload_with_correct_params(Mink $mink, Session $session, Local $localImageDriver)
{
$mink->getSession()->willReturn($session);
$session->getScreenshot()->willReturn('binary-image');
$localImageDriver->upload('binary-image', 'test.png')->shouldBeCalled();
$this->takeScreenshot('test.png');
}
}
You should mock the call to outputFactory->createOutput() that is in StreamOutputPrinter line 144, but mocking something that is in another class is a smell. So I'd recommend to move the stream logic to a new class, eg StreamOutputPrinterFactory, and inject this factory:
public function __construct(Mink $mink, StreamOutputPrinterFactory $factory, array $imageDrivers)
{
$this->mink = $mink;
$this->imageDrivers = $imageDrivers;
$this->outputStream = $factory->createNew();
}
Now you can mock any calls to $this->outputStream.
You should also call createNew() when it's needed, not in the constructor. Let me know if you need further help.
Related
I have an artisan command which gets some options, one of these options is --type=, like below:
protected $signature = 'make:procedure {name} {--type=}';
--type= contains the kind of difference, I want to check this option in the stub because each type has a different namespace which should be used in the stub.
for example, this is my stub:
<?php
namespace DummyNamespace;
class DummyClass
{
//
}
How can I do this, (of course this is an example, I just trying to explain my problem):
<?php
namespace DummyNamespace;
if ($type === 'one') {
echo 'use App\Some\Namespace\One'
}
class DummyClass
{
//
}
It would be highly appreciated if anyone can advise me!😊
your Custom command should derive from GeneratorCommand then you can use abstract Method getStub()
Your Stub File
namespace DummyNamespace;
/**
* Class DummyClass.
*/
class DummyClass
{
}
In Your Command File, you just need to use below code
/**
* Get the stub file for the generator.
*
* #return string
*/
protected function getStub()
{
return app_path('file/path/test.stub');
}
For Explanation Only
In GeneratorCommand class
/**
* Get the stub file for the generator.
*
* #return string
*/
abstract protected function getStub();
/**
* Build the class with the given name.
*
* #param string $name
* #return string
*
* #throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
protected function buildClass($name)
{
$stub = $this->files->get($this->getStub());
return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
}
A very simple way to generate files from stub.
In your stub file
namespace {{namespace}};
/**
* Class {{name}}.
*/
class {{name}}
{
}
Somewhere in your command
protected function getStub()
{
return file_get_contents(resource_path('stubs/dummy.stub'));
}
protected function generate($namespace, $name)
{
$template = str_replace(
['{{namespace}}', '{{name}}'],
[$namespace, $name],
$this->getStub()
);
file_put_contents(app_path("Dummies/$name.php"), $template);
}
I am trying to inject an service object into my Repository. I have created different Service Classes under the directory Classes/Services. There is also one class that I created called ContainerService, which creates and instantiate one ServiceObject for each Service Class.
ContainerService Class:
namespace VendorName\MyExt\Service;
use VendorName\MyExt\Service\RestClientService;
class ContainerService {
private $restClient;
private $otherService;
/**
* #return RestClientService
*/
public function getRestClient() {
$objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
if ($this->restClient === null) {
$this->restClient = $objectManager->get(RestClientService::class);
}
return $this->restClient;
}
...
As I said, I create my ServiceObjects in the ContainerService Class.
Now I want to inject the ContainerService into my Repository and use it.
MyRepository Class:
namespace VendorName\MyExt\Domain\Repository;
use VendorName\MyExt\Service\ContainerService;
class MyRepository extends Repository
{
/**
* #var ContainerService
*/
public $containerService;
/**
* inject the ContainerService
*
* #param ContainerService $containerService
* #return void
*/
public function injectContainerService(ContainerService $containerService) {
$this->containerService = $containerService;
}
// Use Objects from The ContainerService
public function findAddress($addressId) {
$url = 'Person/getAddressbyId/'
$someData = $this->containerService->getRestClient()->sendRequest($url)
return $someData;
}
In MyController I recieve the $someData from my findAddress function and do some work with it.
But when I call my Page, I get following ErrorMessage:
(1/2) #1278450972 TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException
Class ContainerService does not exist. Reflection failed.
Already tried to reload all Caches and dumping the Autoload didn't help either.
Didn't install TYPO3 with composer.
I appreciate any advice or help! Thanks!
Actually found the Issue.
In MyRepository Class there was a Problem with the Annotations and the TypeHint:
namespace VendorName\MyExt\Domain\Repository;
use VendorName\MyExt\Service\ContainerService;
class MyRepository extends Repository
{
/**
*** #var \VendorName\MyExt\Service\ContainerService**
*/
public $containerService;
/**
* inject the ContainerService
*
* #param \VendorName\MyExt\Service\ContainerService $containerService
* #return void
*/
public function injectContainerService(\VendorName\MyExt\Service\ContainerService $containerService) {
$this->containerService = $containerService;
}
// Use Objects from The ContainerService
public function findAddress($addressId) {
$url = 'Person/getAddressbyId/'
$someData = $this->containerService->getRestClient()->sendRequest($url)
return $someData;
}
Now it works.
I have tried for days to get this to work.
I am using this framework: https://github.com/DennisSkoko/discord-bot to create a bot. But I wan't to add the DIC container http://container.thephpleague.com/ to this project.
What I want to do is in the Main class is to register service providers (modules/packages/bundles) or whatever you want to call them, just like Laravel etc. does.
So I created a module/ dir and added a serviceprovider class and some other classes as the docs state: http://container.thephpleague.com/service-providers/
First I edited the start.php to inject the container:
use League\Container\Container;
require "vendor/autoload.php";
require "bot.php";
$container = new Container;
$main = new DS\Main(new Example($container));
$main->run();
I changed the Example bot to include a getContainer() function so I could read it from the Main class, like this:
use DS\Bot;
use Discord\Discord;
use DS\Service;
class Example extends Bot
{
/**
* Example constructor
*/
public function __construct($container)
{
$this->config = [
"token" => "My token"
];
$this->container = $container;
}
/**
* Will be executed when the WebSocket is ready and before the services are implemented.
*
* #param Discord $discord
*
* #return void
*/
public function setup(Discord $discord)
{
echo "Example is ready to start!";
}
public function getContainer()
{
return $this->container;
}
/**
* Will return an array of Service that the bot uses.
*
* #return Service[]
*/
public function getServices()
{
return [];
}
}
I then changed the Main class to to register my service provider.
namespace DS;
use Discord\Discord;
/**
* A class that bring bots script into life.
*/
class Main
{
/**
* #var Discord
*/
private $discord;
/**
* #var Bot
*/
private $bot;
/**
* Main constructor.
*
* #param Bot $bot
*/
public function __construct(Bot $bot)
{
$this->bot = $bot;
$this->discord = new Discord($this->bot->getConfig());
$this->discord->on("ready", function ($discord) {
$this->bot->setup($discord);
$this->setServices($this->bot->getServices());
});
$this->container = $this->bot->getContainer();
$this->container->addServiceProvider('Mynamespace\HelloWorld\ServiceProvider');
}
/**
* Will run the bot.
*
* #return void
*/
public function run()
{
$this->discord->run();
}
/**
* Will add the services for the WebSocket.
*
* #param Service[] $services
*
* #return void
*/
private function setServices($services)
{
foreach ($services as $service) {
$this->discord->on($service->getEvent(), $service->getListener());
}
}
}
Here is the problem:
PHP Fatal error: Uncaught exception 'InvalidArgumentException' with message 'A service provider must be a fully qualified class name or instance of (\League\Container\ServiceProvider\ServiceProviderInterface)'
Which is weird because I did it just like in the documentation and extended League\Container\ServiceProvider\AbstractServiceProvider
This I have double checked 20 times.
So I have no idea what to do about this. How can I use addServiceProvider() in the Main class and register things in the DIC?
I do not really want to do it in the Example class if possible. Because that should be extended by the user and the Main class will bootstrap the bot.
I also tried $this->bot->addServiceProvider() but then I get the following error:
PHP Fatal error: Call to undefined method Example::addServiceProvider()
Any help appreciated.
I found some strange behavior with mockup builder, can someone explain to me why this happen?
here is my test code:
class PlaceTest extends \PHPUnit_Framework_TestCase
{
const API_KEY = 'test-api';
public function testConstruct()
{
$google = $this->getMockBuilder('GusDeCooL\GooglePhp\Google')
->setConstructorArgs(array(self::API_KEY))
->setMethods(array('getKey'))
->getMock();
$google->expects($this->any())
->method('getKey')
->will($this->returnValue(self::API_KEY));
/* #var $google \GusDeCooL\GooglePhp\Google */
$place = new Place($google);
$this->assertInstanceOf('GusDeCooL\GooglePhp\Component\Place\Place', $place);
$this->assertInstanceOf('GusDeCooL\GooglePhp\Google', $place->getParent());
$this->assertEquals(self::API_KEY, $place->getKey());
return $place;
}
/**
* #param Place $place
*
* #depends testConstruct
*/
public function testGetKey(Place $place)
{
$this->assertInstanceOf('GusDeCooL\GooglePhp\Google', $place->getParent());
$this->assertEquals(self::API_KEY, $place->getKey());
}
}
And here is the code of actual class
<?php
namespace GusDeCooL\GooglePhp\Component\Place;
use GusDeCooL\GooglePhp\Component\ChildInterface;
use GusDeCooL\GooglePhp\Google;
use GusDeCooL\GooglePhp\Place\Nearby;
class Place implements ChildInterface
{
/**
* #var Google
*/
private $parent;
/**
* #var Nearby
*/
private $nearby;
public function __construct(Google $parent)
{
$this->setParent($parent);
}
/**
* API Key
* #return string
*/
public function getKey()
{
return $this->getParent()->getKey();
}
}
While running the test, the PlaceTest::testConstruct() while doing $place->getKey() it pass the test but it errors in PlaceTest::testGetKey()
How is that happen?
It seems this is the limitation of mockup builder.
http://phpunit.de/manual/3.7/en/test-doubles.html
we can't pass mockup object into another test scope.
Please leave a comment if you found another reason.
I have a sample class attached, that when I try to generate the test file for it, using the PHPUnit-Skelgen 1.2.0 on PHP 5.4.11, I do not get the namespace added to the file, so the test fails. However, all the methods are picked up.
Source File:
<?php
namespace lib\Parameters;
class COMMAND_LINE implements \Iterator
{
private $CommandLineOptions_ = array();
/**
* Create the Command Line Options Class
* #param boolean $AddHelpOption - Optionally add the -h/--help option automatically
*/
public function __construct($AddHelpOption = TRUE)
{
if( $AddHelpOption == TRUE)
{
}
}
/**
* An internal pointer to the current position in the Command Line Options
* #var integer
*/
protected $Position = 0;
/**
* This method takes the pointer back to the beginning of the dataset to restart the iteration
*/
public function rewind()
{
$this->Position = 0;
}
/**
* This method returns the value at the current position of the dataset
* #return COMMAND_LINE_OPTION
*/
public function current()
{
return $this->CommandLineOptions_[$this->Position];
}
/**
* Return the current value of the pointer
* #return integer
*/
public function key()
{
return $this->Position;
}
/**
* Move the pointer to the next element
*/
public function next()
{
++ $this->Position;
}
/**
* Returns where the next item is valid or not
* #return boolean
*/
public function valid()
{
return isset($this->CommandLineOptions_[$this->Position]);
}
}
?>
Command line, with the class in the current directory:
>phpunit-skelgen --test -- lib\Parameters\COMMAND_LINE COMMAND_LINE.class COMMAND_LINE_Test COMMAND_LINE.class.test
PHPUnit Skeleton Generator 1.2.0 by Sebastian Bergmann.
Wrote skeleton for "COMMAND_LINE_Test" to "COMMAND_LINE.class.test".
Test class created has the functions, but does not have the Namespace:
<?php
/**
* Generated by PHPUnit_SkeletonGenerator 1.2.0 on 2013-02-04 at 17:50:23.
*/
class COMMAND_LINE_Test extends PHPUnit_Framework_TestCase
{
/**
* #var COMMAND_LINE
*/
protected $object;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->object = new COMMAND_LINE;
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
}
/**
* #covers lib\Parameters\COMMAND_LINE::rewind
* #todo Implement testRewind().
*/
public function testRewind()
{
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet.'
);
}
/**
* #covers lib\Parameters\COMMAND_LINE::current
* #todo Implement testCurrent().
*/
public function testCurrent()
{
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet.'
);
}
/**
* #covers lib\Parameters\COMMAND_LINE::key
* #todo Implement testKey().
*/
public function testKey()
{
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet.'
);
}
/**
* #covers lib\Parameters\COMMAND_LINE::next
* #todo Implement testNext().
*/
public function testNext()
{
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet.'
);
}
/**
* #covers lib\Parameters\COMMAND_LINE::valid
* #todo Implement testValid().
*/
public function testValid()
{
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet.'
);
}
}
Trying it from the root directory with relative file paths does not work either. If I use a fully qualified namespace (which I can not, as I need to be relative), then the class is not located, and only the setUp and tearDown methods are found.
You can give namespace to your generated test class like this.
>phpunit-skelgen --test -- lib\Parameters\COMMAND_LINE COMMAND_LINE.class lib\Parameters\COMMAND_LINE_Test COMMAND_LINE.class.test