PHPUnit 9 - Mocking void methods - unit-testing

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');

Related

Testing against early returns (defensive programming)

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.

Testing that one class method calls another

Imagine I have the following class.
class SomeClass {
public function shortcutMethod($arg1) {
return $this->method($arg1, 'something');
}
public function method($arg1, $arg2) {
// some stuff
}
}
So the shortcutMethod is a shortcut to the other method. Let us say I want to write a test that given and $arg1 the shortcutMethod will correctly call method with the correct arguments.
So far I think I figured I need to mock the class to expect a call to method with some arguments and then call shortcutMethod on the mock object like so (note I am using Mockery).
$mock = m::mock("SomeClass");
$mock = $mock->shouldReceive('method')->times(1)->withArgs([
'foo',
'something'
]);
$mock->shortcutMethod('foo');
This results in an exception like so shortcutMethod() does not exist on this mock object.
Did I misunderstand the usage for mocking? I understand it makes more sense for objects that are dependency injected into the class, but what in this scenario? How would you go about it? And perhabs more importantly, is this sort of testing useless, and if so, why?
You should use mocking to mock out the dependencies of the class under test, not the class under test itself. After all, you are trying to test the real behavior of your class.
Your example is a little basic. How you would test such a class would depend on what your method function does. If it returns a value that is in turn returned by shortCutMethod then I would say that your should just be asserting the output of shortCutMethod. Any dependencies within the method function should be mocked (methods belonging to other classes). I'm not that familiar with mockery, but I've given a tweaked version of your example a go.
class SomeClass {
private $dependency;
public function __construct($mockedObject) {
$this->dependency = $mockedObject;
}
public function shortcutMethod($arg1) {
return $this->method($arg1, 'something');
}
public function method($arg1, $arg2) {
return $this->dependency->mockedMethod($arg1, $arg2);
}
}
$mock = m::mock("mockedClass");
$mock->shouldReceive('mockedMethod')->times(1)->withArgs([
'foo',
'something'
])->andReturn('returnedValue');
$testCase = new SomeClass($mock);
$this->assertEquals(
'returnedValue',
$testCase->shortcutMethod('foo')
);
Having said that, it is possible to partially mock your class under test so that you can test the real behavior of the shortCutMethod function but mock out the method function to assert that it is called with the expected arguments. Have a look at partial mocks.
http://docs.mockery.io/en/latest/reference/partial_mocks.html

Mockery\Exception\NoMatchingExpectationException PHPUnit test fails

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);
}

Add constant to a mock in PHPUnit

Is it possible to add a class constant to a mock using PHPUnit?
Here an example:
class SomeTest extends PHPUnit_Framework_TestCase {
public function setUp() {
$mock = $this->getMock( 'SomeClass' );
// Here I'd like to add a constant to $mock; something like
// $mock::FOOBAR;
}
}
Does any of you know how can I get this behavious to work?
Thx!
This question has been around a while with no answers, but I ran into this same problem. This does not appear to be possible; however, there's at least one dirty work-around:
In your test file
<?php
class SomeClass {
const FOOBAR = 'foobar';
}
class SomeTest extends PHPUnit_Framework_TestCase {
public function setUp() {
$mock = $this->getMock( 'SomeClass' );
}
}
// tests
?>
Then, you use your mocked object for mocked functionality, and you use the class constant the same way you would have originally. For example:
// Call a method on mocked object
// (would need to add this method to your mock, of course)
$mock->doSomething();
// Use the constant
$fooBar = SomeClass::FOOBAR;
This is dirty, so I'm sure things could get pretty messed up if you're using some sort of autoloading that tries to load the actual SomeClass class, but this will work "fine" if you're not loading the original SomeClass.
I'm definitely interested to hear other solutions as well as get some feedback on just how dirty this really is.

In Moq, is there a simple way to make Setup() return null, regardless of all arguments supplied?

Using Moq, I want to Setup() a call, so that it always returns null, regardless of any supplied parameters.
I do it like this:
_myMock.Setup(mock => mock.MyMethod(
It.IsAny<int?>(),
It.IsAny<String>(),
It.IsAny<String>(),
It.IsAny<String>())).
Returns((IList<Item>)null
);
Quite lenghty for just returning null. Can I make it simpler?
Just don't make the setup, with the default MockBehavior.Loose it will return default values - null for classes, 0 for numbers, the default value for structs.
Caveat: if the return type is IEnumerable or Array, it will return and empty set, not null. In that case, you need an explicit setup.
It's very strange that it does not return empty IList though, as IList is IEnumerable. Probably it's a bug, but anyway, works for what you asked for :)
Both these examples work (using also FluentAssertions and NUnit, besides Moq):
public interface ISomeDummy
{
IList<int> Nums(int i);
}
[Test]
public void NullSetupTestWithMockOf()
{
var mock = Mock.Of<ISomeDummy>();
var items = mock.Nums(1);
items.Should().BeNull();
}
[Test]
public void NullSetupTestWithoutSetup()
{
var mock = new Mock<ISomeDummy>();
var items = mock.Object.Nums(1);
items.Should().BeNull();
}
No, you can't. Your method requires four parameters, so you gotta supply them.