I have a a PHPUnit test where the third method won't receive the array returned in the second method. Take a look:
<?php
namespace Tests\Unit;
use Tests\TestCase;
class TesteTest extends TestCase
{
public function testFirst()
{
$test = ['id' => 123];
$this->assertNotEmpty($test);
return $test;
}
/** #depends testFirst */
public function testSecond(array $test)
{
$this->assertNotEmpty($test);
}
/** #depends testSecond */
public function testThird(array $test)
{
$this->assertNotEmpty($test);
}
}
This the response I have from PHPUnit:
Argument 1 passed to Tests\Unit\TesteTest::testThird() must be of the type array, null given, called in C:\Users\edgar\Documents\Projetos\Solarium\solarium-api\vendor\phpunit\phpunit\src\Framework\TestCase.php on line 1527
Any ideas of where I might be going wrong?
Idea is simple. Check documentation.
A producer is a test method that yields its unit under test as return value.
A consumer is a test method that depends on one or more producers and their return values.
Your producer testSecond return nothing to your consumer testThird
public function testSecond(array $test)
{
$this->assertNotEmpty($test);
return $test;
}
Related
I just upgraded the phpunit 7.5.20 to phpunit 9.5.0 and I'm facing a lot of errors (good ones actually), but not 100% sure how to workaround with some of those errors.
Just looking for some ideas to fix the following error:
Method setDummyStuff may not return value of type NULL, its return declaration is "void"
It happens only when you're creating a createConfiguredMock() and passing a null method as a argument.
Here's my test:
<?php
use Lib\IDummyCode;
class DummyTest extends PHPUnit\Framework\TestCase
{
public function setUp(): void
{
parent::setUp();
}
public function testDummyThatReturnsVoid()
{
$this->createConfiguredMock(IDummyCode::class, [
'setDummyStuff' => null
]);
}
}
And here's the dummy class:
<?php
namespace Lib;
interface IDummyCode
{
public function setDummyStuff(
int $testInt,
string $testString
): void;
}
Do you guys, have some thoughts about how to improve this?
Thanks a lot!
The second parameter to createConfiguredMock takes an associative array where the key is the method to mock and the value is the value the method should return. Since the setDummyStuff method can not return anything (void return type) it does not make sense to define a return value. It isn't the null value in particular. It will fail with any value.
So you could just leave that method out:
$mock = $this->createConfiguredMock(IDummyCode::class, []);
Which can also be written in a nicer way:
$mock = $this->createStub(IDummyCode::class);
If you need to verify that setDummyStuff was called, you have to set up an expectation.
$mock = $this->createMock(IDummyCode::class);
$mock->expects(self::once())
->method('setDummyStuff')
->with(123, 'Hello');
I have some doubt what to inject. Given this code:
class A
{
public function getSomething()
{
return 'something';
}
}
class TestMe
{
/**
* #var A
*/
private $a;
public function __construct($a)
{
$this->a = $a;
}
public function greetings()
{
return 'Hello, '.$this->a->getSomething();
}
}
my test A:
function testA()
{
$a = new class() {
public function getSomething()
{
return 'aAnonimus';
}
};
$sut = new TestMe($a);
$this->assertEquals($sut->greetings(), 'Hello, aAnonimus');
}
testB, same but with mock:
function testA()
{
$a = $this->createMock(A::class);
$a->method('getSomething')->willReturn('bMockery');
$sut = new TestMe($a);
$this->assertEquals($sut->greetings(), 'Hello, bMockery');
}
in the first test I simply inject a plain object.
But the second its more Phpunit's way: using mocked objects.
Question is, for long period which one wins? I find the first more conviement, and for the 2nd test, you have to know the class name of dependency (otherwise you cant create a mock)
In the longterm, it's better the second way because it's better to have type hint in the constructor which will not allow you to provide a simple object.
Also when we are talking about UnitTests you should test a certain class without depending on 3rd party libraries or other services logic. So the best way is to use mocks for all of the services which are part of the tested class
I am learning testing and trying to test a function using 'early returns'. The function on success sets a property in another class and on failure simply returns so in both cases it 'returns' void.
class Test
{
private $fileHandler;
private $config;
public __constructor($fileHandler, $config)
{
$this->fileHandler = $fileHandler;
$this->config = $config;
}
public function example($filePath)
{
$exists = $this->fileHandler->exists($filePath);
if ($exists === false) {
return;
}
$this->config->set($filePath);
}
}
In this example I believe I can test this with two unit tests and by mocking the fileHandler class.
For a failure (early return) the $config class's method set() should not be called whilst for a success the method should be called.
However, this test passes if I try and change never() to once() making me think the entire test is bogus.
/** test */
public function config_is_not_set_with_missing_file()
{
$fileHandlerMock = $this->getMockBuilder(fileHandler::class)->getMock;
$fileHandlerMock->method('exists')
->willReturn('false');
$configMock = $this->getMockBuilder(config::class)->getMock;
$test = new Test($fileHandlerMock, $configMock);
$test->example('fake file path');
$configMock->expects($this->never())
->method('set');
}
Your file handler mock is returning the string 'false', which is !== to false. Change that to false and Tets::example should return early.
You're not passing the $configMock to the Test constructor, so it's not being used.
You're right, if the test passes both with once and never expectations, the test is not working as expected and requires reviewing it.
I have problem unit testing method inside closure called by call_user_func() example :
public function trans($lang, $callback)
{
$this->sitepress->switch_lang($lang);
call_user_func($callback);
}
on controller :
public function sendMail()
{
$foo = $baz = 'something';
$mail = $this->mailer;
$this->helper->trans_c('en', function() use($foo, $baz, $mail) {
$mail->send('Subject', $foo, $baz);
});
}
test case :
public function testSomething()
{
$helperMock = Mockery::mock('Acme\Helper');
$helperMock->shouldReceive('trans_c')->once(); // passed
$mailMock = Mockery::mock('Acme\Mail');
$mailMock->shouldReceive('send')->once(); // got should be called 1 times instead 0
$act = new SendMailController($helperMock, $mailMock);
$act->sendMail();
}
how can I ensure that ->send() method is called inside closure trans_c()
I tried with
$helperMock->shouldReceive('trans_c')->with('en', function() use($mailMock) {
$mailMock->shouldReceive('send');
});
no luck. :(
well it works fine with passing Mockery::type('Closure') in the second param of trans_c, but I really need to ensure that method send from mailer class is called.
A mocked class does not execute the real code by default. If you mock the helper it will check that the calls are being made but won't execute the anonymous function.
With mockery, you can configure the expectation so that the real method will be executed: passthru();
Try this:
$helperMock = Mockery::mock('Acme\Helper');
$helperMock
->shouldReceive('trans_c')
->once()
->passthru()
;
This is explained in the docs.
EDIT
Maybe you don't really need to mock the helper. If you mock the Mail class and expect the send method to be called once, just let the real helper do it.
Im testing with PHPUnit and my test fails on a function. But i don't know why.
The function i want to mock:
public function subscribe($email)
{
$message = new SubscribeMessage();
$message->setEmailaddress($email);
$message->setLocale(Locale::getDefault());
$this->getAmqpProducer()->publish($message, 'newsletter-subscribe');
return true;
}
and my Unit test:
public function testSubscribeSendsAmqpMessage()
{
$email = 'email#email.nl';
$locale = 'nl';
$this->amqpProducerMock
->shouldReceive('publish')
->once()
->with(
\Mockery::on(
function ($message, $routingkey) use (&$publishedMessage) {
$publishedMessage = $message;
return $routingkey == 'newsletter-subscribe';
}
)
);
$this->service->subscribe($email, $locale);
}
but the test says:
Mockery\Exception\NoMatchingExpectationException : No matching handler found for AcsiRabbitMq\Producer\Producer::publish(AcsiNewsletter\RabbitMq\Message\SubscribeMessage, "newsletter-subscribe"). Either the method was unexpected or its arguments matched no expected argument list for this method
How can i fix my Unit test? Or how can i refactor my test?
You Mock the subscribe, not the internal publish. When you run the test and call ->subscribe, it will attempt to execute the code in the class. Therefore, it will try to run the subscribe() method, which you appear to have a strange reference to your Mock.
Normally, your test will mock the subscribe, so you can return a value for the assert test, which is hard coded.
You appear to have tried to mock the GetAmqpProducer() object that is in your regular code. You need to either be able to pass the mock object to be used into your class, or to be able to assign it.
Simplified Example:
class Email
{
private $MsgObject;
// Constructor Injection
public __construct(SubscribeMessage $MessageObject)
{
$this->MsgObject = $MessageObject;
...
}
// Setter Injection
public function SetSubscribeMessage(Subscribe $MessageObject)
{
$this->MsgObject = $MessageObject;
}
public function setEmailaddress($email)
{
$this->MsgObject->emailAddress = $email;
...
}
public function setLocale($Locale)
{
$this->MsgObject->Locale = $Locale;
...
}
...
}
Your class sample above has too many internal objects and dependencies to be tested as such, since the test will actually call these. You would use Dependency Injection to pass the objects with known state, and have them return properly.
Please note, I am not showing how to do this in Mockery, as I do not use it, but this simple example should help you understand what I am trying to express.
So a simple test might look like:
public function testSubscribeMessage()
{
$email = 'email#email.nl';
$this->Mock(
->shouldReceive('setEmailAddress')
->once()
->will_return($email)
);
$SubscribeMessage = new SubscribeMessage($this->Mock);
$SetEmail = $SubscribeMessage->setEmailAddress($email);
$this->assertEquals($email, $SetEmail);
}