I'm making a simple laravel package to wrap bash executions and make it be able to mock as laravel facade, but I have a problem.
Most of Php functions that runs bash commands uses pass-by-reference parameters, and the question is, how can I mock the return value of the pass-by-reference of the method?
MyFacade:
class MyFacade
{
public function run(string $command, &$return_val): string
{
return system($command, $return_val);
}
}
My test:
$passByReferenceValue = 1;
MyFacade::shouldReceive('run')->with('ping google.com', $passByReferenceValue)
->once()->andReturn("PING RESULT");
$return = MyFacade::run('ping google.com', $passByReferenceValue);
$this->assertEquals(1, $passByReferenceValue);
$this->assertEquals("PING RESULT", $return);
It actually works because I set the value of $passByReferenceValue and it's not changed. What I want is to pass a null pointer as second parameter of run method and make the mock to change it.
Example:
MyFacade::shouldReceive('run')->with('ping google.com', SOME_MAGIC_CODE_RETURNS_1)
->once()->andReturn("PING RESULT");
$resultCode = null;
$return = MyFacade::run('ping www.google.com', $resultCode)
$this->assertEquals(1, $resultCode);
$this->assertEquals("PING RESULT", $return);
Thanks
Related
I am attempting to call one Artisan (Laravel) command from another command. However, I need to be able to retrieve an array from the command that is called from the "main" command...
i.e
// Command 1
public function handle() {
$returnedValue = $this->call( 'test:command' );
dump( $returnedValue ); // <-- is 5
}
// Command 2
public function handle() {
return $this->returnValue();
}
private function returnValue() {
$val = 5;
return $val;
}
I have looked through the documentation and can't find a way to do this, so I was wondering if there was a way or if I need to re-think my approach.
Thanks!
Artisan Commands don't behave the same way as, for example, Controller functions. They return an exitCode, which in my testing was always 0 (couldn't get anything to return if an error is thrown).
Your approach won't work if you try to get a return value, but you can access \Artisan::output(); to see what exactly is sent by the first artisan command you call.
// FirstCommand.php
public function handle(){
\Artisan::call("second:command");
if(\Artisan::output() == 1){
$this->info("This Worked");
} else {
$this->error("This Didn't Work");
}
}
Note: I used \Artisan::call(); there's some apparent differences between the two where using $this->call() didn't work as expected, but \Artisan::call() did. $this->call() sent both 0 and 1 back, regardless of the actual code being executed; not sure what's up there. Tested on Laravel 5.0, which is quite behind the current, so maybe that's it.
// SecondCommand.php
public function handle(){
try {
$test = 1 / 1;
} catch (\Exception $ex){
$this->error("0");
}
$this->info("1");
}
Running php artisan first:command in my console returns:
$ php artisan first:command
This Worked
Now, if switch the code in $test to
$test = 1 / 0;
I get this in my console:
$ php artisan first:command
This Didn't Work
So, the rule here I guess is to avoid outputting anything in the second command prior to the result you want to check with \Artisan::output().
I'm currently creating a php artisan console command in a Laravel 5.1 project, and want to call another console command from my console command. This third party command I want to call does not accept any options or arguments, but rather receives its input via interactive questions.
I know I can call a command with options and arguments like this:
$this->call('command:name', ['argument' => 'foo', '--option' => 'bar']);
I also know I can call an interactive command without interactions like this from the command line:
php artisan command:name --no-interaction
But how can I answer these interactive questions from within my command?
I would like to do something like the below (pseudo code).
$this->call('command:name', [
'argument' => 'foo',
'--option' => 'bar'
], function($console) {
$console->writeln('Yes'); //answer an interactive question
$console-writeln('No'); //answer an interactive question
$console->writeln(''); //skip answering an interactive question
} );
Of course the above doesn't work, since $this->call($command, $arguments) does not accept a third callback parameter.
How can I answer interactive questions when calling a console command from a console command?
I have another solution, it is to call a symfony command executing 'php artisan' instead of using artisan sub-commands. I think that's better than patching 3rd party code.
Here is a trait which manages this.
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
trait ArtisanCommandTrait{
public function executeArtisanCommand($command, $options){
$stmt = 'php artisan '. $command . ' ' . $this->prepareOptions($options);
$process = new Process($stmt);
$process->run();
// executes after the command finishes
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process->getOutput();
}
public function prepareOptions($options){
$args = [];
$opts = [];
$flags = [];
foreach ($options as $key => $value) {
if(ctype_alpha(substr($key, 0, 1)))
$args[] = $value;
else if(starts_with($key, '--')){
$opts[] = $key. (is_null($value) ? '' : '=' . $value) ;
}
else if(starts_with($key, '-')){
$flags[] = $key;
}
}
return implode(' ', $args) . ' '
.implode(' ', $opts). ' '
.implode(' ', $flags);
}
}
Now, you should be able to pass any artisan special options such as no-interaction.
public function handle(){
$options = [
'argument' => $argument,
'--option' => $options, // options should be preceded by --
'-n' => null // no-interaction option
];
$command = 'your:command';
$output = $this->executeArtisanCommand($command, $options);
echo $output;
}
You can download the trait from this gist
Here's how I did it.
Beware: this patches the core Symfony class QuestionHelper#doAsk, and although this code runs fine for my purposes (I'm currently just making a proof of concept), this code should probably not run in any production environment.
I'm not accepting my own answer yet, would like to know if there's a better way to do this.
The following assumes a Laravel 5.1 installation.
First composer-require the Patchwork package. I'm using this to augment the functionality of that Symfony class method.
composer require antecedent/patchwork
Edit bootstrap/app.php and add the following right after the application is created. (Patchwork is not autoloaded)
if($app->runningInConsole()) {
require_once(__DIR__ . '/../vendor/antecedent/patchwork/Patchwork.php');
};
Add the following two use statements to the top of your console command class
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
augment/patch QuestionHelper#doAsk by using these helper methods on your console command class
public function __construct() {
parent::__construct();
$this->patchAskingQuestion();
}
/**
* Patch QuestionHelper#doAsk
* When a key 'qh-patch-answers' is found in the $_REQUEST superglobal,
* We assume this is an array which holds the answers for our interactive questions.
* shift each answer off the array, before answering the corresponding question.
* When an answer has a NULL value, we will just provide the default answer (= skip question)
*/
private function patchAskingQuestion() {
\Patchwork\replace('Symfony\Component\Console\Helper\QuestionHelper::doAsk', function(OutputInterface $output, Question $question) {
$answers = &$_REQUEST['qh-patch-answers'];
//No predefined answer found? Just call the original method
if(empty($answers)) {
return \Patchwork\callOriginal([$output, $question]);
}
//using the next predefined answer, or the default if the predefined answer was NULL
$answer = array_shift($answers);
return ($answer === null) ? $question->getDefault() : $answer;
});
}
private function setPredefinedAnswers($answers) {
$_REQUEST['qh-patch-answers'] = $answers;
}
private function clearPredefinedAnswers() {
unset($_REQUEST['qh-patch-answers']);
}
You can now answer interactive questions like this
public function fire() {
//predefine the answers to the interactive questions
$this->setPredefinedAnswers([
'Yes', //first question will be answered with 'Yes'
'No', //second question will be answered with 'No'
null, //third question will be skipped (using the default answer)
null, //fourth question will be skipped (using the default answer)
]);
//call the interactive command
$this->call('command:name');
//clean up, so future calls to QuestionHelper#doAsk will definitely call the original method
$this->clearPredefinedAnswers();
}
With mpyw/streamable-console: Call interactive artisan command using arbitrary stream:
$this->usingInputStream("yes\nno\n")->call('command:name');
I'm using Artisan::call() in one of my routes and would like to save the command output to a variable.
Is there any way to capture the STDOUT and STDERR generated by the artisan command?
This is a way:
use Symfony\Component\Console\Output\BufferedOutput;
Route::get('/test', function()
{
$output = new BufferedOutput;
Artisan::call('list', array(), $output);
return $output->fetch();
});
When running a command from inside another command, here is how to get all the styling:
public function handle()
{
Artisan::call('other:command', [], $this->getOutput());
}
Seems the previous answers don't work in Laravel 5.2 any more (not sure about 5.1)
You can now use Artisan::output();
$output = '';
if (!Schema::hasTable('migrations')) {
Artisan::call('migrate:install', array());
$output .= Artisan::output();
}
// Updates the migration, then seed the database
Artisan::call('migrate:refresh', array('--force' => 1));
$output .= Artisan::output();
Artisan::call('db:seed', array('--force' => 1));
$output .= Artisan::output();
dd($output);
I had a unique solution that works well for me
In my base Command class which all my other commands extend I overloaded the output functions and then also If I need to I can overload them again in any extended commands... obviously writeLogFile is another custom function i made, however you can do whatever you want there
public function info($message,$verbosity = null)
{
$this->writeLogFile($message);
parent::info($message);
}
public function warn($message,$verbosity = null)
{
$this->writeLogFile($message);
parent::warn($message);
}
public function error($message,$verbosity = null)
{
$this->writeLogFile($message);
parent::error($message);
}
I have an interesting problem and have searched the internet, but haven't yet found an answer.
I work for a company that doesn't allow it's workers to utilize OOP, it is kind of ridiculous, but the working experience is valuable.
Consider the following function:
function get_setting_values_from_file( $parameter )
{
exec("/usr/var/binary --options $parameter", $output, $return);
$settings = file( $output[0] );
foreach( $settings as $setting ) {
if( strstr( $setting, "color") ) {
$setting = explode( ":", $setting );
return $setting[1];
}
}
return false;
}
I need to unit test a similar function. I am currently using phpUnit for my tests and the vfsStream libraries to mock the file system, but how do you mock the call to exec("/usr/var/binary --options $parameter", $output, $return) when I'm developing with no access to the actual system? What is the recommend approach for dealing with test cases like this?
All feedback is appreciated.
You could mock exec() by using a function mock library. I made one (php-mock) for you which requires you to use namespaces
namespace foo;
use phpmock\phpunit\PHPMock;
class ExecTest extends \PHPUnit_Framework_TestCase
{
use PHPMock;
public function testExec()
{
$mock = $this->getFunctionMock(__NAMESPACE__, "exec");
$mock->expects($this->once())->willReturnCallback(
function ($command, &$output, &$return_var) {
$this->assertEquals("foo", $command);
$output = "failure";
$return_var = 1;
}
);
exec("foo", $output, $return_var);
$this->assertEquals("failure", $output);
$this->assertEquals(1, $return_var);
}
}
Simply mock this function to return the text that you are trying to get into $settings. You do not need to call the executable, simply create the file or return.
For instance, assuming the function get_setting_values_from_file() returns the settings as an array, you can simply mock the function in your test to return the settings as an array. Create a test stub to mock the object that contains the get_setting_values_from_file() method, and have that mock simply return the same FALSE, 1 or 2 that the test assumed.
$stub = $this->getMock('GetSettingsClass');
$stub->expects($this->any())
->method('get_settings_from_file')
->will($this->returnValue(0));
This is from the PHPUnit manual -> http://phpunit.de/manual/3.8/en/test-doubles.html#test-doubles.stubs
Optionally, you could even bypass the call, and simply test the functions/code that works on the returns by creating the array and passing it to those functions.
Assumed Example in the main code:
...
$settings = get_setting_values_from_file( 'UserType' );
$UserType = get_user_type($settings);
return $UserType;
function get_user_type($settings)
{
if($settings !== FALSE) // Returned from your function if parameter is not found
{
switch($settings)
{
case 1:
return 'User'; // Best to use Constants, but for example here only
break;
case 2:
return 'Admin';
break;
...
}
}
else
{
return FALSE;
}
}
Now, in your test, you can simply
$this->assertFalse(get_user_type(FALSE, 'Ensure not found data is handled properly as FALSE is returned');
$this->assertEqual('User', get_user_type(1), 'Test UserType=1');
$this->assertEqual('Admin', get_user_type(1), 'Test UserType=2');
...
These work as the code does not call the function that had to mock the read from the OS, but does handle all the expected returns by calling the function processing the setting return value. Here, you have simply assumed the return from the function 'get_setting_values_from_file()' without needing the file or any mocks.
This does NOT however test reading from the file, which I would do in another test by using the setUp and tearDown to actual create a file with the values you want (fopen/fwrite) and then call your function and ensure it returns what is expected.
I hope this helps to explain what I was thinking.
Is it possible to configure PHPUnit mock in this way?
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->any())
->method('offsetGet')
->with('Matcher')
->will($this->returnValue(new Matcher()));
$context->expects($this->any())
->method('offsetGet')
->with('Logger')
->will($this->returnValue(new Logger()));
I use PHPUnit 3.5.10 and it fails when I ask for Matcher because it expects "Logger" argument.
It is like the second expectation is rewriting the first one, but when I dump the mock, everything looks ok.
Sadly this is not possible with the default PHPUnit Mock API.
I can see two options that can get you close to something like this:
Using ->at($x)
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->at(0))
->method('offsetGet')
->with('Matcher')
->will($this->returnValue(new Matcher()));
$context->expects($this->at(1))
->method('offsetGet')
->with('Logger')
->will($this->returnValue(new Logger()));
This will work fine but you are testing more than you should (mainly that it gets called with matcher first, and that is an implementation detail).
Also this will fail if you have more than one call to each of of the functions!
Accepting both parameters and using returnCallBack
This is more work but works nicer since you don't depend on the order of the calls:
Working example:
<?php
class FooTest extends PHPUnit_Framework_TestCase {
public function testX() {
$context = $this->getMockBuilder('Context')
->getMock();
$context->expects($this->exactly(2))
->method('offsetGet')
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
->will($this->returnCallback(
function($param) {
var_dump(func_get_args());
// The first arg will be Matcher or Logger
// so something like "return new $param" should work here
}
));
$context->offsetGet("Matcher");
$context->offsetGet("Logger");
}
}
class Context {
public function offsetGet() { echo "org"; }
}
This will output:
/*
$ phpunit footest.php
PHPUnit 3.5.11 by Sebastian Bergmann.
array(1) {
[0]=>
string(7) "Matcher"
}
array(1) {
[0]=>
string(6) "Logger"
}
.
Time: 0 seconds, Memory: 3.00Mb
OK (1 test, 1 assertion)
I've used $this->exactly(2) in the matcher to show that this does also work with counting the invocations. If you don't need that swapping it out for $this->any() will, of course, work.
As of PHPUnit 3.6, there is $this->returnValueMap() which may be used to return different values depending on the given parameters to the method stub.
You can achieve this with a callback:
class MockTest extends PHPUnit_Framework_TestCase
{
/**
* #dataProvider provideExpectedInstance
*/
public function testMockReturnsInstance($expectedInstance)
{
$context = $this->getMock('Context');
$context->expects($this->any())
->method('offsetGet')
// Accept any of "Matcher" or "Logger" for first argument
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
// Return what was passed to offsetGet as a new instance
->will($this->returnCallback(
function($arg1) {
return new $arg1;
}
));
$this->assertInstanceOf(
$expectedInstance,
$context->offsetGet($expectedInstance)
);
}
public function provideExpectedInstance()
{
return array_chunk(array('Matcher', 'Logger'), 1);
}
}
Should pass for any "Logger" or "Matcher" arguments passed to the Context Mock's offsetGet method:
F:\Work\code\gordon\sandbox>phpunit NewFileTest.php
PHPUnit 3.5.13 by Sebastian Bergmann.
..
Time: 0 seconds, Memory: 3.25Mb
OK (2 tests, 4 assertions)
As you can see, PHPUnit ran two tests. One for each dataProvider value. And in each of those tests it made the assertion for with() and the one for instanceOf, hence four assertions.
Following on from the answer of #edorian and the comments (#MarijnHuizendveld) regarding ensuring that the method is called with both Matcher and Logger, and not simply twice with either Matcher or Logger, here is an example.
$expectedArguments = array('Matcher', 'Logger');
$context->expects($this->exactly(2))
->method('offsetGet')
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
->will($this->returnCallback(
function($param) use (&$expectedArguments){
if(($key = array_search($param, $expectedArguments)) !== false) {
// remove called argument from list
unset($expectedArguments[$key]);
}
// The first arg will be Matcher or Logger
// so something like "return new $param" should work here
}
));
// perform actions...
// check all arguments removed
$this->assertEquals(array(), $expectedArguments, 'Method offsetGet not called with all required arguments');
This is with PHPUnit 3.7.
If the method you are testing doesn't actually return anything, and you simply need to test that it is called with the correct arguments, the same approach applies. For this scenario, I also attempted doing this using a callback function for $this->callback as the argument to the with, rather than returnCallback in the will. This fails, as internally phpunit calls the callback twice in the process of verifying the argument matcher callback. This means that the approach fails as on the second call that argument has already been removed from the expected arguments array. I don't know why phpunit calls it twice (seems an unnecessary waste), and I guess you could work around that by only removing it on the second call, but I wasn't confident enough that this is intended and consistent phpunit behaviour to rely on that occurring.
My 2 cents to the topic: pay attention when using at($x): it means that expected method call will be the ($x+1)th method call on the mock object; it doesn't mean that will be the ($x+1)th call of the expected method. This made me waste some time so I hope it won't with you. Kind regards to everyone.
I just stumbled on this PHP extension to mock objects: https://github.com/etsy/phpunit-extensions/wiki/Mock-Object
Here are also some solutions with the doublit library :
Solution 1 : using Stubs::returnValueMap
/* Get a dummy double instance */
$double = Doublit::dummy_instance(Context::class);
/* Test the "offsetGet" method */
$double::_method('offsetGet')
// Test that the first argument is equal to "Matcher" or "Logger"
->args([Constraints::logicalOr('Matcher', 'Logger')])
// Return "new Matcher()" when first argument is "Matcher"
// Return "new Logger()" when first argument is "Logger"
->stub(Stubs::returnValueMap([['Matcher'], ['Logger']], [new Matcher(), new Logger()]));
Solution 2 : using a callback
/* Get a dummy double instance */
$double = Doublit::dummy_instance(Context::class);
/* Test the "offsetGet" method */
$double::_method('offsetGet')
// Test that the first argument is equal to "Matcher" or "Logger"
->args([Constraints::logicalOr('Matcher', 'Logger')])
// Return "new Matcher()" when first argument $arg is "Matcher"
// Return "new Logger()" when first argument $arg is "Logger"
->stub(function($arg){
if($arg == 'Matcher'){
return new Matcher();
} else if($arg == 'Logger'){
return new Logger();
}
});