Soft assertion and mock methods test failure - php

I`m following this tutorial: http://jtreminio.com/2013/03/unit-testing-tutorial-part-5-mock-methods-and-overriding-constructors/ . A great tutorial for learn how works PHPUnit.
But I´m not able to understand because the test not pass.
The failure is:
Method was expected to be called 1 times, actually called 0 times.
At this part of code:
$badCode->expects($this->once())
->method('checkPassword')
->with($password);
But this is not possible because the next soft assertion runs inside checkPassword method and pass the test.
$badCode->expects($this->once())
->method('callExit');
It fails because is a mock method and the behaviour is different? Or the code is wrong?
I attach all the files for easy understanding, it is a small example.
Console
PHPUnit 3.7.18 by Sebastian Bergmann.
FYOU SHALL NOT PASS............
Time: 0 seconds, Memory: 6.00Mb
There was 1 failure:
1) phpUnitTutorial\Test\BadCodeTest::testAuthorizeExitsWhenPasswordNotSet
Expectation failed for method name is equal to <string:checkPassword> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
FAILURES!
Tests: 13, Assertions: 14, Failures: 1.
BadCode.php
<?php
namespace phpUnitTutorial;
class BadCode
{
protected $user;
public function __construct(array $user)
{
$this->user = $user;
}
public function authorize($password)
{
if ($this->checkPassword($password)) {
return true;
}
return false;
}
protected function checkPassword($password)
{
if (empty($user['password']) || $user['password'] !== $password) {
echo 'YOU SHALL NOT PASS';
$this->callExit();
}
return true;
}
protected function callExit()
{
exit;
}
}
BadCodeTest.php
<?php
namespace phpUnitTutorial\Test;
class BadCodeTest extends \PHPUnit_Framework_TestCase
{
public function testAuthorizeExitsWhenPasswordNotSet()
{
$user = array('username' => 'jtreminio');
$password = 'foo';
$badCode = $this->getMockBuilder('phpUnitTutorial\BadCode')
->setConstructorArgs(array($user))
->setMethods(array('callExit'))
->getMock();
$badCode->expects($this->once())
->method('checkPassword')
->with($password);
$badCode->expects($this->once())
->method('callExit');
$this->expectOutputString('YOU SHALL NOT PASS');
$badCode->authorize($password);
}
}
Someone can help me? Thanks!
Update
The author of the blog updated the tutorial with the solution.
Can't do any assertions against mock methods, only stubs.
BadCode.php
<?php
namespace phpUnitTutorial;
class BadCode
{
protected $user;
public function __construct(array $user)
{
$this->user = $user;
}
public function authorize($password)
{
if ($this->checkPassword($password)) {
return true;
}
return false;
}
protected function checkPassword($password)
{
if (empty($this->user['password']) || $this->user['password'] !== $password) {
echo 'YOU SHALL NOT PASS';
$this->callExit();
}
return true;
}
protected function callExit()
{
exit;
}
}
BadCodeTest.php
<?php
namespace phpUnitTutorial\Test;
class BadCodeTest extends \PHPUnit_Framework_TestCase
{
public function testAuthorizeExitsWhenPasswordNotSet()
{
$user = array('username' => 'jtreminio');
$password = 'foo';
$badCode = $this->getMockBuilder('phpUnitTutorial\BadCode')
->setConstructorArgs(array($user))
->setMethods(array('callExit'))
$badCode->expects($this->once())
->method('callExit');
$this->expectOutputString('YOU SHALL NOT PASS');
$badCode->authorize($password);
}
}

author here - I fudged that part up, and AppFog (my host) is having issues with free accounts (mine) so I can't update it right now.
Also, yes it should be $this->user in checkPassword()

You're missing a method in setMethods :
->setMethods(array('callExit'))
You need to add checkPassword too if you want to check if it is called.

Related

PHPUnit mock method from another class

With PHPUnit 6.5.14, I am trying to test a method. To do this, one of its dependencies needs to be mocked; however I can't get it working. Here is a stripped down version:
class Order {
public function create() {
$CCP = new CreditCardProcessor();
$success = $CCP->chargeCreditCard();
return $success;
}
}
class CreditCardProcessor {
public function chargeCreditCard() {
return false;
}
}
class OrderTest extends TestCase {
public function testCreate() {
$mockCCP = $this->getMockBuilder(CreditCardProcessor::class)
->setMethods(['chargeCreditCard'])
->getMock();
$mockCCP
->method('chargeCreditCard')
->willReturn(true);
$O = new Order();
$success = $O->create();
$this->assertTrue($success, 'Was not able to create order.');
}
}
I've read the docs and gone over some examples but can't figure it out. Any ideas what I am doing wrong? Thanks.
After finding more examples, I believe the solution is to pass in the dependency to create as an argument:
class Order {
public function create($CCP) {
$success = $CCP->chargeCreditCard();
return $success;
}
}
Then I can do the same in my test:
class OrderTest extends TestCase {
public function testCreate() {
$mockCCP = $this->getMockBuilder(CreditCardProcessor::class)
->setMethods(['chargeCreditCard'])
->getMock();
$mockCCP
->method('chargeCreditCard')
->willReturn(true);
$O = new Order();
$success = $O->create($mockCCP);
$this->assertTrue($success, 'Was not able to create order.');
}
}
I haven't tried yet, but that should do it.
I don't like the idea of changing code to satisfy tests, but it probably is also an indication I need to restructure my code.

How do I pass parameters in PHP unit testing

I am trying to learn how to create unit tests for my custom "framework" and here is a method that verifies the email address when user is registering.
private function verifyEmail()
{
if(empty($this->email) || empty($this->email_repeat)) {
throw new \Exception('please enter email');
}
if($this->email != $this->email_repeat) {
throw new \Exception('emails don\'t match');
}
if(!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
throw new \Exception('E-mail is invalid');
}
$isTaken = $this->db->getRow("SELECT COUNT(*) as count FROM users WHERE email = ?", [$this->email]);
if($isTaken->count > 0){
throw new \Exception('E-mail is taken');
}
}
And here is the unit test
class RegisterTest extends \PHPUnit\Framework\TestCase
{
public function testVerifyEmail() {
// what do i type here?
}
}
So, what do I type in the testVerifyEmail() method to pass an email to be tested? I am going through the documentation but as a newbie the information is overwhelming and I can't find a solution.
You can use PhpUnit DataProvider to provide parameters for your test methods.
https://phpunit.de/manual/6.5/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
The example here will execute testMethod 4 times (one for each $data item).
<?php
use PHPUnit\Framework\TestCase;
class DataTest extends TestCase
{
/**
* #dataProvider myProvider
*/
public function testMethod($a, $b, $expected)
{
var_dump($a,$b,$expected);
//... your assertions here
}
public function myProvider()
{
$data = [
//each item represents the related method parameter
//the first time $a = 'valueOfA-0', $b='valueOfB-0',$expected='valueOfExpected-0'
//and so on, for each array
['valueOfA-0', 'valueOfB-0', 'valueOfExpected-0'],
['valueOfA-1', 'valueOfB-1', 'valueOfExpected-1'],
['valueOfA-2', 'valueOfB-2', 'valueOfExpected-2'],
['valueOfA-3', 'valueOfB-3', 'valueOfExpected-3'],
];
return $data;
}
}
//values of testMethod parameters each time
//$a = 'valueOfA-0', $b='valueOfB-0', $expected='valueOfExpected-0'
//$a = 'valueOfA-1', $b='valueOfB-1', $expected='valueOfExpected-1'
//$a = 'valueOfA-2', $b='valueOfB-2', $expected='valueOfExpected-2'
//$a = 'valueOfA-3', $b='valueOfB-3', $expected='valueOfExpected-3'

How to properly use properties of a class as arguments of its methods

I want to use a private method in a class called by public methods from the same class.
class Foo
{
private $updateAll = true;
private $updateA = false;
private $updateB = false;
public function updateA()
{
$this->updateAll = false;
$this->updateA = true;
$this->updateB = false;
$this->update();
}
public function updateB()
{
$this->updateAll = false;
$this->updateA = false;
$this->updateB = true;
$this->update();
}
private function update()
{
// Code here...
if ($this->updateAll) {
// set all api call params
}
if ($this->updateA) {
// set api call param
}
if ($this->updateB) {
// set api call param
}
// Code here...
}
}
Is this a proper use of class properties as arguments?
It works but I don't know whether there is a better way to do this. My purpose is to kinda use dynamic method arguments without the need to pass 3 arguments to update().
Your code is not wrong and should work just fine as you say, but it does feel a bit weird... I think any of the following approaches is cleaner and more flexible than your code. I'm sure there will be lots of other options perfectly valid, these are just some ideas...
Multiple method arguments
As it's been suggested to you in the comments, I think the normal way to do that is actually just adding the arguments to the update() method...
class Updater
{
public function update($all = true, $a = false, $b = false)
{
// Code...
}
}
One constant method argument
However, in your example, the options seem to be mutually exclusive (any combination of 2 options is redundant), so you can do perfectly fine with just one parameter!
class Updater
{
const UPDATE_ALL = 'all';
const UPDATE_A = 'a';
const UPDATE_B = 'b';
public function update($updateMode = self::UPDATE_ALL)
{
// Code...
}
}
Command pattern
If your example is not realistic, and you have a scenario with lots of options that are not mutually exclusive, I'd use something similar to a command pattern, where the class in charge to define the options of the operations is different from the class that performs the operation...
class Updater
{
public function update(UpdateCommand $command)
{
// Code...
}
}
class UpdateCommand
{
public $paramA = false;
public $paramB = false;
// ...
public $paramZ = false;
}
Fluent interface
Or you could also use a fluent interface. Although that's a bit harder to test...
class Updater
{
private $paramA = false;
private $paramB = false;
// ...
private $paramZ = false;
public function withA()
{
$this->paramA = true;
return $this;
}
public function withB()
{
$this->paramB = true;
return $this;
}
// ...
public function withZ()
{
$this->paramZ = true;
return $this;
}
public function run()
{
// Code...
}
}

PhpUnit mock included class method

I have two classes, ClassA, ClassB. In ClassA I have Method which call method from ClassB.
For Example:
function functionInClassB($state)
{
if ($state) {
return true;
} else {
return false;
}
}
function functionInClassA ()
{
if (functionInClassB(1)) {
return "Anything";
}
}
Now, I want test functionInClassB with PhpUnit and I dont want functionInClassB ran. I want return value which I want.
Sorry for my English, please help!!!
Old question, but I think you're referring to stubbing / mocking.
How easy /sensible it is depends. If you want to call functionInClassB statically then you're out of luck; static stuff doesn't work well with testing. But if you're calling the method on an instance then the following should work:
class A {
protected $instanceOfB;
public function functionInClassA(B $injectedInstanceOfB) {
if ($injectedInstanceOfB->functionInClassB(1)) return 'anything';
}
}
class B {
public function functionInClassB($state) {
if ($state) {
return true;
} else {
return false;
}
}
}
class yourPHPUnitTest extends \PHPUnit\Framework\TestCase {
public function testA() {
$stubOfB = $this->createStub(\B::class);
// Optionally...
$stubOfB->method('functionInClassB')->willReturn('something');
// Thing you want to test
$a = new A();
$result = $a->functionInClassA($stubOfB);
$this->assertEqual('something', $result);
}
}

How to unit-test an abstract class, when its constructor executes its abstract method(-s)?

I'm writing PHPUnit unit tests for this Zend Framework 2 class:
<?php
namespace MyNamespace\InputFilter;
use Zend\InputFilter\Input as ZendInput;
use Zend\InputFilter\Factory;
use Zend\InputFilter\InputInterface;
use MyNamespace\Exception\InvalidParameterException;
abstract class AbstractInput extends ZendInput
{
final public function __construct()
{
// The constructor uses/executes the abstract method getInputSpecification().
$this->init($this->getInputSpecification());
}
public function merge(InputInterface $input)
{
$mergedInput = parent::merge($input);
$mergedInput->setFallbackValue($input->getFallbackValue());
return $mergedInput;
}
public function isValid()
{
$this->injectNotEmptyValidator();
$validator = $this->getValidatorChain();
$value = $this->getValue();
$result = $validator->isValid($value, $context);
return $result;
}
protected function init($inputSpecification)
{
$factory = new Factory();
$tempInput = $factory->createInput($inputSpecification);
$this->merge($tempInput);
}
abstract protected function getInputSpecification();
public function getFinalValue($param)
{
$finalValue = null;
if (empty($param) || $this->getValue() === '') {
if ($this->getFallbackValue() !== null) {
$finalValue = $this->getFallbackValue();
} else {
throw new InvalidParameterException('The parameter ' . $this->name . ' must be set!');
}
} else {
if ($this->isValid()) {
$finalValue = $this->getValue();
} else {
throw new InvalidParameterException('The parameter ' . $this->name . ' is invalid!');
}
}
return $finalValue;
}
}
and get a problem with this. This code
// ...
class AbstractInputTest extends TestCase
{
// ...
public function setUp()
{
$stub = $this->getMockForAbstractClass('\MyNamespace\InputFilter\AbstractInput');
$stub
->expects($this->any())
->method('getInputSpecification')
->will($this->returnValue(array())
);
// ...
}
// ...
}
causes an error:
$ phpunit SgtrTest/InputFilter/AbstractInputTest.php PHPUnit 3.7.21 by Sebastian Bergmann.
Configuration read from /path/to/project/tests/phpunit.xml
E...
Time: 1 second, Memory: 7,00Mb
There was 1 error:
1) SgtrTest\InputFilter\AbstractInputTest::testMerge
Zend\InputFilter\Exception\InvalidArgumentException:
Zend\InputFilter\Factory::createInput expects an array or Traversable;
received "NULL"
/path/to/project/vendor/ZF2/library/Zend/InputFilter/Factory.php:98
/path/to/project/vendor/SGTR/library/MyNamespace/InputFilter/AbstractInput.php:72
/path/to/project/vendor/SGTR/library/MyNamespace/InputFilter/AbstractInput.php:31
/path/to/project/tests/SgtrTest/InputFilter/AbstractInputTest.php:20
FAILURES! Tests: 4, Assertions: 0, Errors: 1.
I undestand, why the error is thrown. Bu how to to resolve this in another way?
In my test, I create a quick class extending the abstract class, then I test the concrete methods in that class.
class TestAbstractInput extends AbstractInput
{
...
}
class AbstractInputTest extends TestCase
{
// ...
public function setUp()
{
$this->TestClassObject = new TestAbstractInput();
}
public function test_isValid()
{
$this->assertEquals(1, $this->TestClassObject->isValid);
}
}
After this, you can then use the either a Mock object of the Abstract class created in the test, or mock the Abstract class as you have done.

Categories