I am developping a phpcas bundle using guard component of Symfony framework. My bundle is working but I want to do some unit tests. I want to test my CasAuthenticator. PhpCAS library is using static method. So I decided to use Mock Aspect to mock it.
I configured Aspect, but I still have a bug.
Here is a simplified test which is running but failing.
Expected PhpCAS::setDebug to be invoked but it never occurred. Got:
C:\wamp64\www\casguard\casguard\vendor\codeception\aspect-mock\src\AspectMock\Proxy\Verifier.php:64
C:\wamp64\www\casguard\casguard\Tests\SimpleTest.php:32
//root_dir/Tests/SimpleTest.php
namespace AlexandreT\Bundle\CasGuardBundle\Tests;
use PHPUnit\Framework\TestCase;
use AspectMock\Test as test;
use PhpCAS;
class SimpleTest extends TestCase
{
public function testAspectMock()
{
$phpCas = test::double('PhpCAS', ['setDebug' => function () {
echo 'YES I CALL THE MOCKED Debug function';
}]);
PhpCAS::setDebug();
$phpCas->verifyInvoked('setDebug', false);
}
protected function tearDown()
{
parent::tearDown();
test::clean();
}
}
Output does not contain YES I CALL THE MOCKED Debug function, so I think that the PhpCAS is not mocked by Aspect.
I carefully read this documentation and I configured my bootstrap file like this:
//root_dir/Tests/bootstrap.php
include __DIR__.'/../vendor/autoload.php'; // composer autoload
$kernel = \AspectMock\Kernel::getInstance();
$kernel->init([
'debug' => true,
'includePaths' => [
__DIR__.'/../vendor/jasig/phpcas',
],
]);
As you can read, I added the vendor directory where the Cas.php declares the PhpCAS class. But it doesn't change anything. I made some tests: the bootstrap.php file is loaded by phpunit.
What did I miss in my Mock Aspect configuration?
I added a line to configure cache directory in my bootstrap.php file
and I can see that the Cas.php was well included.
But I still had the bug. When I was exploring the cached file, I discovered that the phpcas library doesn't respect the PSR0 convention. First letter of phpCAS Class isn't capitalized.
So I edited my test:
public function testAspectMock()
{
//$phpCas = test::double('PhpCAS', ['setDebug' => function () {
// ^
// |
// v
$phpCas = test::double('phpCAS', ['setDebug' => function () {
echo 'YES I CALL THE MOCKED Debug function';
}]);
phpCAS::setDebug(); //phpCAS instead of PhpCAS
$phpCas->verifyInvoked('setDebug', false);
//And I had an assertion else test is marked as risky.
self::expectOutputString('YES I CALL THE MOCKED Debug function');
}
2 letters... 2 hours of debugging... #Grrrr
Related
I'm trying to test a middleware that uses the some config() values but it doesn't work. I'm getting
Illuminate\Contracts\Container\BindingResolutionException: Target class [config] does not exist.
This is not a new issue in the framework (https://github.com/laravel/framework/issues/9733) but I just can't get it to work and it was closed in an unsatisfactory manner.
I've tried :
Not using the config helper and instead using the Facade, but the facade root is not set in a testing environment.
Using a new instance of the config repository itself but well... it's empty.
Any ideas?
My middleware's handle function:
public function handle($request, Closure $next)
{
$result = $this->apiCall()
if($result->fails) { // Evaluates to true
throw new \Exception('message with config value'.config('option')); // throws error
}
return $next($request);
}
Test function
public function testMiddleware()
{
$request = Request::create('/login', 'POST');
$middleware = new MyMiddleware;
$this->expectException(\Exception::class);
$response = $middleware->handle($request, function() {});
}
The reason this test wasn't working was very dumb. I created the test with the command
php artisan make:test MyTestCase --unit
Since I passed the --unit flag, it was extending PHPUnit's TestCase class instead of Laravel's own.
I have something like the following set up in Laravel:
In /app/controllers/MyController.php:
class MyController extends BaseController {
const MAX_FILE_SIZE = 10000;
// ....
}
In /app/tests/MyControllerTest.php:
class MyControllerTest extends TestCase {
public function myDataProvider() {
return [
[ MyController::MAX_FILE_SIZE ]
];
}
/**
* #dataProvider myDataProvider
*/
public function testMyController($a) {
// Just an example
$this->assertTrue(1 == 1);
}
}
However, when I run vendor/bin/phpunit I get the following error:
PHP Fatal error: Class 'Controller' not found in /home/me/my-app/app/controllers/BaseController.php on line 3
Fatal error: Class 'Controller' not found in /home/me/my-app/app/controllers/BaseController.php on line 3
If I remove the reference to the MyController class in myDataProvider() and replace it with a literal constant then the test completes successfully.
In addition, I can place references to MyController::MAX_FILE_SIZE inside the actual testMyController() method, and the test also completes successfully.
It appears that the autoloading setup for Laravel framework classes isn't being set up until after the data provider method is being called, but before the actual test methods are called. Is there any way around this so that I can access Laravel framework classes from within a PHPUnit data provider?
NOTE: I'm calling PHPUnit directly from the command line and not from within an IDE (such as NetBeans). I know some people have had issues with that, but I don't think that applies to my problem.
As implied in this answer, this appears to be related to the order that PHPUnit will call any data providers and the setUp() method in any test cases.
PHPUnit will call the data provider methods before running any tests. Before each test it will also call the setUp() method in the test case. Laravel hooks into the setUp() method to call $this->createApplication() which will add the controller classes to the 'include path' so that they can be autoloaded correctly.
Since the data provider methods are run before this happens then any references to controller classes inside a data provider fail. It's possible work around this by modifying the test class to something like this:
class MyControllerTest extends TestCase {
public function __construct($name = null, array $data = array(), $dataName = '') {
parent::__construct($name, $data, $dataName);
$this->createApplication();
}
public function myDataProvider() {
return [
[ MyController::MAX_FILE_SIZE ]
];
}
/**
* #dataProvider myDataProvider
*/
public function testMyController($a) {
// Just an example
$this->assertTrue(1 == 1);
}
}
This will call createApplication() before the data provider methods are run, and so there is a valid application instance that will allow the appropriate classes to be autoloaded correctly.
This seems to work, but I'm not sure if it's the best solution, or if it is likely to cause any issues (although I can't think of any reasons why it should).
The test will initialize much faster if you create the application right within the dataProvider method, especially if you have large set of items to test.
public function myDataProvider() {
$this->createApplication();
return [
[ MyController::MAX_FILE_SIZE ]
];
}
Performance warning for the other solutions (especially if you plan to use factories inside your dataProviders):
As this article explains:
The test runner builds a test suite by scanning all of your test
directories […] When a
#dataProvider annotation is found, the referenced data provider is
EXECUTED, then a TestCase is created and added to the TestSuite for
each dataset in the provider.
[…]
if you use factory methods in your data providers, these
factories will run once for each test utilizing this data provider
BEFORE your first test even runs. So a data provider […] that is used by ten tests
will run ten times before your first
test even runs. This could drastically slow down the time until your
first test executes. Even […] using phpunit --filter,
every data provider will still run multiple times. Filtering occurs after the test
suite has been generated and therefore after any
data providers have been executed.
The above article proposes to return a closure from the dataProvider and execute that in your test:
/**
* #test
* #dataProvider paymentProcessorProvider
*/
public function user_can_charge_an_amount($paymentProcessorProvider)
{
$paymentProcessorProvider();
$paymentProcessor = $this->app->make(PaymentProviderContract::class);
$paymentProcessor->charge(2000);
$this->assertEquals(2000, $paymentProcessor->totalCharges());
}
public function paymentProcessorProvider()
{
return [
'Braintree processor' => [function () {
$container = Container::getInstance();
$container->bind(PaymentProviderContract::class, BraintreeProvider::class);
}],
...
];
}
You can adjust this behaviour of PHPUnit by adding your custom bootstrapper to your projects phpunit.xml like this (look at 3rd line):
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="tests/bootstrap.php" ← ← ← THIS
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
...
</phpunit>
Then create a bootstrap.php file in your tests folder (i.e. the path you denoted above), and paste this:
<?php
use Illuminate\Contracts\Console\Kernel;
require __DIR__ . '/../vendor/autoload.php';
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
You can now use Laravel functionality in your data providers, just keep in mind they still run after your setUp methods.
I'm trying to get Mockery to assert that a given method is called at least once.
My test class is:
use \Mockery as m;
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testSetUriIsCalled()
{
$uri = 'http://localhost';
$httpClient = m::mock('Zend\Http\Client');
$httpClient->shouldReceive('setUri')->with($uri)->atLeast()->once();
}
}
As you can see, there's one test that (hopefully) creates an expectation that setUri will be called. Since there isn't any other code involved, I can't imagine that it could be called and yet my test passes. Can anyone explain why?
You need to call Mockery:close() to run verifications for your expectations. It also handles the cleanup of the mockery container for the next testcase.
public function tearDown()
{
parent::tearDown();
m::close();
}
To avoid having to call the close method in every test class, you can just add the TestListener to your phpunit config like so:
<listeners>
<listener class="\Mockery\Adapter\Phpunit\TestListener"></listener>
</listeners>
This approach is explained in the docs.
One thing to note from the linked docs is:
Make sure Composer’s or Mockery’s autoloader is present in the bootstrap file or you will need to also define a “file” attribute pointing to the file of the above TestListener class.
Just a sidenote: If you use Laravel: the make:test --unit generates a test class that extends the original PhpUnit Testcase class and not the included Tests\Testcase, which loads the laravel app and runs the Mockery::close(). It is also the reason why in some cases your tests fail if you use Laravel specific code (like Cache, DB or Storage) in the units you're testing.
so if you need to test units with Laravel specific code, just swap out the 'extends Testcase' and there is no need to call Mockery::close() manually
I'm trying to write some test cases for a ZF application I'm writing but I can't seem get past this point.
I have extended the Zend_Test_PHPUnit_ControllerTestCase, adding the following:
public function setUp() {
$this->bootstrap = new Zend_Application('testing',
APPLICATION_PATH . '/configs/application.ini');
parent::setUp();
}
It works, and it initializes the Zend Autoloader, allowing me to load other classes inside my library as well as Zend classes. So I setup a test:
public function testUserUnauthorized() {
$this->dispatch('/api/user');
//Assertions...
}
Sadly, the test never gets past the dispatch. Instead, it gives me an error:
Fatal error: Class 'App_Model_User' not found in ....Action.php
Action.php is a class extending Zend_Controller_Action. In it, there is a function that uses App_Model_User to authenticate the logged in user. I never had to add a require_once since the Autoloader works. That's the strange part: it works through my browser. Just not in PHPUnit.
So I dumped the Autoloaders registered with the Zend Autoloader:
public function testUserUnauthorized() {
print_r(Zend_Loader_Autoloader::getInstance()->getAutoloaders());
exit;
$this->dispatch('/api/user');
}
It shows all my modules, as well as the namespaces for views, controllers, models, etc. I did the same through my browser and the dumps matched.
Zend Autoloader needs App_ added as an autoload prefix for this to work as you expect, something like this:
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace("App_");
For our project I have created a framework on top of the PHPUnit framework which helps us in some of the common tasks in writing unit tests.
This custom framework inherits from PHPUnit_Framework_TestCase and then modifies the mySetup() and adds bunch of useful functions for our code.
<?php
class OurUnitTestFramework extends PHPUnit_Framework_TestCase {
public $dbMock;
protected function mySetup (..) { ... }
protected function testHelper () { ... }
}
?>
Now in our test code we just extend OurUnitTestFramework and then write the tests.
<?php
require_once ("OurUnitTestFramework");
class DatabaseConnectionTest extends OurUnitTestFramework {
parent::setUp (..) { ... }
public function testSomeThing () { ... }
public function testSomeOtherThing () { ... }
}
?>
Till now we were running all the unit tests through Jenkins and it still is running fine but now when we try to run the tests in a folder it fails. All the tests inside the folder/sub-folder runs successfully but there is one failure:
[sumit#dev model]$ phpunit database
PHPUnit 3.5.14 by Sebastian Bergmann.
F........
Time: 0 seconds, Memory: 10.50Mb
There was 1 failure:
1) Warning
No tests found in class "OurUnitTestFramework".
FAILURES!
Tests: 9, Assertions: 30, Failures: 1.
I have a directory database which has sub directories and all the tests passes from that folder and its subfolder but I get failure from OurUnitTestFramework saying there is no tests found in this custom framework. So I am not able to understand why phpunit is running unit tests on the file which is included/extended in the test file?
We can simply choose to ignore this one error but I wanted to know if okay to leave like this or is there something that I need to configure to make it pass.
Thanks
Make 'OurUnitTestFramework' an abstract class.