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.
Related
I am trying to set test expectations on a mock object that is created in a data provider and passed to my test method. This is useful because I can reused my data provider across different test cases and have the tests define what to expect on the mock. However, phpunit marks this test as risky when the case passes, but correctly fails the test when it does not pass. Is this a known thing that cannot be done?
I am using phpunit v9.3
Here is a contrived example to show the problem:
<?php
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class Test extends TestCase
{
public function provideMock(): array
{
return [
[$this->createMock(\DateTime::class)],
];
}
/** #dataProvider provideMock */
public function testMockPasses(MockObject $mock): void
{
$mock->expects($this->once())->method('format')->with('Y-m-d');
$mock->format('Y-m-d');
}
/** #dataProvider provideMock */
public function testMockFails(MockObject $mock): void
{
$mock->expects($this->once())->method('format')->with('Y-m-d');
$mock->format('Y-m-');
}
}
I would expect this to work fine as I am just passing the object to the method - all internal php stuff.
Try running PHPUnit with --verbose key - it may tell more about the reason of marking the test as risky.
I am trying to inject Artisan into a service so I can avoid using the facade.
Looking at the facade class reference I can see that the class I should be injecting is:
Illuminate\Console\Application
So I would assume that doing this:
<?php
namespace App\Service;
use Illuminate\Console\Application;
class DummyDataService
{
/**
* #var Application
*/
private $application;
public function __construct(
Application $application
) {
$this->application = $application;
}
public function insertDummyData()
{
$this->application->call('db:seed', [
'--class' => 'DummyDataSeeder'
]);
}
}
...would work. However, I get the following error:
BindingResolutionException in Container.php line 824:
Unresolvable dependency resolving [Parameter #2 [ <required> $version ]] in class Illuminate\Console\Application
It works if I just call the method on the facade like so:
Artisan::call('db:seed', [
'--class' => 'DummyDataSeeder'
]);
I can't figure out what the problem is so far. Has anyone experienced any similar issues? I try to avoid facades where possible.
Thanks in advance.
You should inject Illuminate\Contracts\Console\Kernel and not Illuminate\Console\Application to achieve what you want, so your class should look like this:
<?php
namespace App\Service;
use Illuminate\Contracts\Console\Kernel;
class DummyDataService
{
private $kernel;
public function __construct(Kernel $kernel)
{
$this->kernel = $kernel;
}
public function insertDummyData()
{
$this->kernel->call('db:seed', [
'--class' => 'DummyDataSeeder'
]);
}
}
If you take a peek at the constructor for Illuminate\Console\Application you will see that it expects a $version parameter and does not provide any sort of default. Therefore, if one is not explicitly provided it will fail because of that dependency. Honestly, this seems like a bug to me. If you look at its Symphony parent class, you will see that it provides a default string of 'UNKNOWN' in its constructor. If you modify Illuminate\Console\Application to have that same default, your commands in the code should now work.
This leaves you with two options.
Just use the Artisan facade for this instance. You should be fine using constructor injection for the rest of the facades, as this affects specifically the Artisan facade.
Change the constructor in the source code to have the default. Not ideal, as all of your changes will be lost every time you update Laravel, but it is an option. You might be able to cook up some sort of service provider that injects a version into all Illuminate\Console\Application instances as well, but I'm not sure.
I am honestly unsure if there are unforeseen ramifications of adding that default into the constructor, although I would imagine they would be minimal as it must be explicitly defined everywhere it is called. I might even make this into a PR and see if Taylor comments on it or just merges it.
I have to write a test class to test my service methods. In my controllers I could access the service by doing $service = $this->get('myService'); and I could access my methods by doing $service->someMethod();.
Now I want to write a test class to test some of the service methods, I tried doing like the doc :
class ServiceTest extends \PHPUnit_Framework_TestCase {
public function testSomeMethod() {
$service = $this->get('myService');
....
}
}
When I launch the test I get something like : Fatal error : Call to undefined method XXX\XXXBundle\Tests\Services\ServicesTest::get() in ...
So my question is how can I use get method to be able to call my service methods
You don't need to use the container in order to test services and other classes.
You should create a new instance of the class and inject the dependencies using test doubles.
Test doubles
- since you don't want to write a functional testing and to use the real dependencies you should be using a test double.
for example if one of the service dependencies is the EntityManager what would happen is that a new entries would be inserted into the Database (or deleted from it) and its not the purpose of Unit testing (you do need to check this tho if you are writing a functional test).
And that's how you should approach this kind of testings:
public function testSomeServiceAdd()
{
$dependencyOne = $this->getMockBuilder('Acme\SomeBundle\DependencyOne')->
disableOriginalConstructor()->
getMock();
$dependencyTwo = $this->getMockBuilder('Acme\SomeBundle\DependencyTwo')->
disableOriginalConstructor()->
getMock();
$service = new SomeService($dependencyOne, $dependencyTwo);
$response = $service->add(1, 2);
$this->assertEquals($response, 3);
}
As you can see I am "mocking" the dependencies and injecting it into the service, after that i'm calling the method and asserting the result.
I would also like to suggest a Mocking framework instead of using the built-in PHPUnit mocking functions. it's easier to use and it has much more mocking functionality:
https://github.com/danrevah/ShortifyPunit
To test your services you should extend the KernelTestCase class.
class ServiceTest extends KernelTestCase
{
private $service;
/**
* {#inheritDoc}
*/
public function setUp()
{
self::bootKernel();
$this->service = static::$kernel->getContainer()
->get('my_service')
;
}
}
I've kind of looking at what's going on Laravel 4 facades under the hood.
Let's take this Facade as an example:
File::get(someArgs);
If i'm not mistaken, the step by step (oversimplified) invocation would be:
//static method invocation which are all extended from Facade class
File::__callStatic(get, someArgs)
//returns an instance of FileSystem
File::resolveFacedeInstance('files')
FileSystem->get(someArgs)
What I'am confused about is in the commented line below of the method File::resolveFacadeInstance() below:
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) return $name;
if (isset(static::$resolvedInstance[$name]))
{
return static::$resolvedInstance[$name];
}
/**
* The line that i'm confused about
*/
return static::$resolvedInstance[$name] = static::$app[$name];
}
My questions are:
How is File::$app even initialized or assigned a value inside the Facade class
If File::get() is the invoked Facade
static::$app[$name] would resolve to i think Application['files']
or Application->files which in turn calls Application->__get('files') since there's no files property inside Application class.
How would FileSystem Class be return if this is only the content of this method?
public function __get($key)
{
return $this[$key];
}
I'll try to describe in short :
So, you already know that resolveFacadeInstance method is called via __callStatic method of Facade class and component's Facade (i.e. File extends Facade) extends this Facade class.
During the boot-up process of the framework, from public/index.php following line starts the execution of bootstrap/start.php file
$app = require_once __DIR__.'/../bootstrap/start.php';
So, in this (bootstrap/start.php) file you can see some code like
// the first line, initiate the application
$app = new Illuminate\Foundation\Application;
// ...
// ...
// notice this line
require $framework.'/Illuminate/Foundation/start.php';
// ...
// last line
return $app;
In this code snippet, require $framework.'/Illuminate/Foundation/start.php'; line starts the execution of Foundation/start.php file and in this file you may see something like this
// ...
Facade::clearResolvedInstances();
// Notice this line
Facade::setFacadeApplication($app);
This (given above) line sets application instanse to $app property in the Facade class
// support/Facades/Facade.php
public static function setFacadeApplication($app)
{
static::$app = $app;
}
Then in the Foundation/start.php file at the bottom, you can see something like this
/*
|--------------------------------------------------------------------------
| Register The Core Service Providers
|--------------------------------------------------------------------------
|
| The Illuminate core service providers register all of the core pieces
| of the Illuminate framework including session, caching, encryption
| and more. It's simply a convenient wrapper for the registration.
|
*/
$providers = $config['providers'];
$app->getProviderRepository()->load($app, $providers);
$app->boot();
In this code snippet given above, all the core components registered by the framework and as you know that, every component has a service provider class (i.e. FilesystemServiceProvider) and in every service provider class there is a method register which is (for FilesystemServiceProvider)
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app['files'] = $this->app->share(function() { return new Filesystem; });
}
Well, in this case $this->app['files'] setting (return new Filesystem) an anonymous function, which returns the filesystem when gets executed
$this->app['files'] = $this->app->share(function() { return new Filesystem; });
to $app['files'] so, when you call the File::get(), it finally calls the anonymous function and in this case, the following line
return static::$resolvedInstance[$name] = static::$app[$name];
Calls the function for static::$app['file']; and this function returns the instance but before returning, it stores the instance in the $resolvedInstance variable, so, next time it can return the instance from the variable without calling the anonymous function again.
So, it looks like that, static::$resolvedInstance[$name] = static::$app[$name]; calls the anonymous function which returns the instance and this function was registered earlier, when the app was started through boot up process.
Important :
Application extends Container and Container extends ArrayAccess class and that's why, a property of the $app object could be (accessed) set/get using array notation.
I've tried to give you an idea but you have to look in to the code, step by step, you won't get it only reading/tracking the code once.
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