laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.39k stars 10.98k forks source link

[BUG][Lumen] Unit test with migrations: Class config does not exist #8547

Closed KarimGeiger closed 9 years ago

KarimGeiger commented 9 years ago

I'm trying to setup unit tests for my Lumen application. Sadly, that only works well for the first test. So I'm assuming Lumen doesn't refresh correctly. Have a look at my TestCase class:

public function createApplication()
{
    return require __DIR__ . '/../bootstrap/app.php';
}

public function artisanMigrateRefresh()
{
    Artisan::call('migrate');
}

public function artisanMigrateReset()
{
   Artisan::call('migrate:reset');
}

When running a test, I'm setting up the database using:

public function setUp()
{
    parent::setUp();
    $this->artisanMigrateRefresh();
}

public function tearDown()
{
    $this->artisanMigrateReset();
    parent::tearDown();  // Moving that call to the top of the function didn't work either.
}

This works well for the first test. The second test fails, saying "ReflectionException: Class config does not exist" (Full error trace).

I've tried loading Lumen once for all tests (just for clarification), and that kind of worked, but as you may have guessed it just made things worse.

The migration (and call) itself works.. I've adjusted the phpunit.xml to use a sqlite memory-database:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>

To reproduce this behaviour, have a look at my test repository. Just check it out, run composer update and then run phpunit. You will see the test failing. Remove the second test at tests/ExampleTest.php, run phpunit again and it will succeed.

Things I've done:

(See the forum post for additional information)

benjaminkimball commented 9 years ago

Changing the setUp method to use createApplication each time fixed this for me.

public function setUp()
{
    parent::setUp();

    $this->createApplication();

    $this->artisanMigrateRefresh();
}

Hope this helps!

KarimGeiger commented 9 years ago

@benjaminkimball: Thanks for the reply. Sadly, that did not fix it for me. You can verify it by checking out my example project.

@GrahamCampbell: please reopen the ticket, since it's not fixed.

benjaminkimball commented 9 years ago

@KarimGeiger Sorry, should have mentioned that you should move the setUp to the base TestCase!

<?php

use Illuminate\Support\Facades\Artisan as Artisan;

class TestCase extends Laravel\Lumen\Testing\TestCase {

    /**
     * Creates the application.
     *
     * @return \Laravel\Lumen\Application
     */
    public function createApplication()
    {
        return require __DIR__.'/../bootstrap/app.php';
    }

    public function setUp()
    {
        parent::setUp();

        $this->createApplication();

        $this->artisanMigrateRefresh();
    }

    public function tearDown()
    {
        $this->artisanMigrateReset();

        parent::tearDown();
    }

    protected function artisanMigrateRefresh()
    {
        Artisan::call('migrate');
    }

    protected function artisanMigrateReset()
    {
        Artisan::call('migrate:reset');
    }
}

I just confirmed this with your test repo!

KarimGeiger commented 9 years ago

Yes, this kind of works, but it won't recreate the database for each test. So if you're trying to test accessing a table, you'll get the following message:

lumen.ERROR: exception 'PDOException' with message 'SQLSTATE[HY000]: General error: 1 no such table: users' 

I've updated the repo to reproduce this state.

benjaminkimball commented 9 years ago

Ah, I see. I'm getting the same error with your test repo. I can't get the DB facade to play nice either. I'm using Eloquent in my project and it's working just fine. I'll try switching one of my controllers over to straight DB and see what happens.

benjaminkimball commented 9 years ago

Sure enough. Switching to not use Eloquent broke my tests as well. I'm at a loss for now!

KarimGeiger commented 9 years ago

Glad I'm not alone with that. And thanks for helping :) Hope we'll find a solution soon.. I'm out of guesses.

benjaminkimball commented 9 years ago

Looked to see if your test repo was still there. I've updated my TestCase to the following and seen better results all around with my tests! Give it a shot!

<?php

use Illuminate\Support\Facades\Artisan as Artisan;

class TestCase extends Laravel\Lumen\Testing\TestCase {

    /**
     * Creates the application.
     *
     * @return \Laravel\Lumen\Application
     */
    public function createApplication()
    {
        return require __DIR__.'/../bootstrap/app.php';
    }

    public function setUp()
    {
        parent::setUp();

        $this->refreshApplication();

        $this->artisan('migrate');
    }

    public function tearDown()
    {
        $this->artisan('migrate:reset');

        parent::tearDown();
    }
}
KarimGeiger commented 9 years ago

Thanks a lot, this seems to work now exactly as expected!

benjaminkimball commented 9 years ago

Sweet!

neylsongularte commented 9 years ago

The facades are the problem. Disable facades.

paunin commented 9 years ago

@neylsongularte , Dont you think its bit stupid solution? :) I mean disable facades? seriously?! And solutions listed here does'not work for me!

neylsongularte commented 9 years ago

Work for me as a palliative solution.

hiraq commented 9 years ago

I have a same issue here, using lumen version 5.0.10, unit test only work if i run each test, when i run all tests, then triggered ReflectionException which said "Class config doesnt exists". I have done "composer dump-autoload" and use "$this->refreshApplication" in my TestCase class, but still not work

hiraq commented 9 years ago

@KarimGeiger hello, are you sure that all of your tests is working now? which lumen version you used?

KarimGeiger commented 9 years ago

@hiraq yes, they all do. My TestCase currently looks like this:

<?php
use Illuminate\Support\Facades\Artisan as Artisan;

class TestCase extends Laravel\Lumen\Testing\TestCase
{

    /**
     * Creates the application.
     *
     * @return \Laravel\Lumen\Application
     */
    public function createApplication()
    {
        return require __DIR__ . '/../bootstrap/app.php';
    }

    public function setUp()
    {
        parent::setUp();

        $this->createApplication();

        $this->artisanMigrateRefresh();
    }

    protected function artisanMigrateRefresh()
    {
        Artisan::call('migrate');
        Artisan::call('db:seed');
    }
}

In the test itself I'm just extending TestCase, not defining setUp() or anything else.

hiraq commented 9 years ago

Unfortunately, if i used $this->createApplication then it will throw another error, when i use $this->call inside my test, it will not send all parameters to controller

and if i used $this->refreshApplication, it just works for first test, the other tests will failed

what version you used @KarimGeiger ?

KarimGeiger commented 9 years ago

I just updated everything using composer, the tests are still working:

laravel/lumen-framework v5.0.10
phpunit/phpunit 4.6.9

In /bootstrap/app.php I've enabled both Facades and Eloquent:

$app->withFacades();

$app->withEloquent();
hiraq commented 9 years ago

Here my test case

<?php

use App\Models\User;
use App\Models\Observers\User as UserObserver;

class UsersControllerTest extends TestCase {

    public function testRegisterSuccess()
    {
        $params = [
            'email' => 'test@test.com',
            'password' => 'testing',
            'password_confirmation' => 'testing'
        ];

        $response = $this->call('POST', '/api/v1/users/register', $params);
        $json = json_decode($response->getContent()); 

        $this->assertResponseOk();

        /*
         * check and test json response
         */ 
        $this->assertTrue(isset($json->status));    
        $this->assertTrue(isset($json->data));    
        $this->assertEquals('success', $json->status); 
        $this->assertTrue(empty($json->data));

        /*
         * check and test database data
         */
        $user = User::where('email', 'test@test.com')->first();
        $this->assertTrue(!empty($user));
        $this->assertTrue(isset($user->email));
        $this->assertEquals('test@test.com', $user->email);        
    }

    public function testRegisterFailedEmailExists()
    {
        $this->generateUser('test@test.com', 'testing');

        $params = [
            'email' => 'test@test.com',
            'password' => 'testing',
            'password_confirmation' => 'testing'
        ];

        $response = $this->call('POST', '/api/v1/users/register', $params);
        $this->assertResponseStatus(400);

        $json = json_decode($response->getContent());

        $this->assertTrue(isset($json->status));
        $this->assertEquals('error', $json->status);

        $this->assertTrue(isset($json->message));
        $this->assertEquals('Fail to validate.', $json->message);
    }

    private function generateUser($email, $password)
    {
        User::observe(new UserObserver);
        $user = new User;
        $user->register($email, $password);
    }
}

and here my TestCase class:

<?php

use Illuminate\Support\Facades\Artisan;
use App\Models\User;

class TestCase extends Laravel\Lumen\Testing\TestCase {

    /**
     * Creates the application.
     *
     * @return \Laravel\Lumen\Application
     */
    public function createApplication()
    {
        return require __DIR__.'/../bootstrap/app.php';
    }

    public function setUp()
    {
        parent::setUp();
        $this->refreshApplication();
        Artisan::call('migrate');
    }

    public function tearDown()
    {
        Artisan::call('migrate:reset');
    }

}
hiraq commented 9 years ago

Here my screenshot if run my tests one by one

lumen_tests

hiraq commented 9 years ago

I cannot use :

$this->createApplication()

Because i can't send my POST parameters (and i dont know why too), so i use :

$this->refreshApplication()
hiraq commented 9 years ago

Ok, it works now, in my case it's caused by :

parent::tearDown()

It looks strange here, when i put that code inside my TestCase class (or anyelse) at "tearDown" method, then all tests broke with ReflectionException error Class config doesn't exists

yeah but thank you guys for your help, at least my tests works now :smile:

polesen commented 9 years ago

What you need to do is add this in setUp()

public function setUp()
{
    parent::setUp();
    \Illuminate\Support\Facades\Facade::clearResolvedInstances();
}

This has been fixed in commit c5d599b086f20a6c0db69ba6ff1f00999ef68654

andersonef commented 8 years ago

I was using laravel, but maybe it could be helpfull. I've rewritted my tearsDown method, exactly like the original, but removing the flush part, so my tearsDown method is like that:

`if ($this->app) { foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { call_user_func($callback); }

        //$this->app->flush(); removed

        //$this->app = null; removed
    }

    if (property_exists($this, 'serverVariables')) {
        $this->serverVariables = [];
    }

    if (class_exists('Mockery')) {
        Mockery::close();
    }`
jonnywilliamson commented 7 years ago

Getting exactly the same issue - here's a little more info.

My tests have a setup and teardown method

    public function setUp()
    {
        parent::setUp();
      //Small couple of lines of my code.
    }

    public function tearDown()
    {
        parent::tearDown();
    }

When my tests run, even though they all pass, I'm left with this as an error message at the end:

PHPUnit 5.0.10 by Sebastian Bergmann and contributors.

Time: 6.36 seconds, Memory: 24.00Mb

OK (2 tests, 2 assertions)

Fatal error: Uncaught ReflectionException: Class config does not exist in /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 749

Call Stack:
    0.0663     788384   1. {main}() /home/vagrant/Code/SourcePhars/phpunit.phar:0
    0.8682    7918344   2. PHPUnit_TextUI_Command::main() /home/vagrant/Code/SourcePhars/phpunit.phar:513
    0.8682    7918456   3. PHPUnit_TextUI_Command->run() phar:///home/vagrant/Code/SourcePhars/phpunit.phar/phpunit/TextUI/Command.php:105

ReflectionException: Class config does not exist in /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 749

Call Stack:
    0.0663     788384   1. {main}() /home/vagrant/Code/SourcePhars/phpunit.phar:0
    0.8682    7918344   2. PHPUnit_TextUI_Command::main() /home/vagrant/Code/SourcePhars/phpunit.phar:513
    0.8682    7918456   3. PHPUnit_TextUI_Command->run() phar:///home/vagrant/Code/SourcePhars/phpunit.phar/phpunit/TextUI/Command.php:105
    6.3677   22266048   4. App\Service\HeatTimer\HeatTimer->__destruct() /home/vagrant/Code/demo.local.test/app/Service/HeatTimer/HeatTimer.php:0
    6.3677   22266048   5. App\Service\HeatTimer\HeatTimer->logout() /home/vagrant/Code/demo.local.test/app/Service/HeatTimer/HeatTimer.php:276
    6.3677   22266048   6. config() /home/vagrant/Code/demo.local.test/app/Service/HeatTimer/HeatTimer.php:287
    6.3677   22266048   7. app() /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php:263
    6.3677   22266104   8. Illuminate\Foundation\Application->make() /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php:106
    6.3677   22266104   9. Illuminate\Container\Container->make() /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:709
    6.3678   22266104  10. Illuminate\Container\Container->build() /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Container/Container.php:644
    6.3678   22266216  11. ReflectionClass->__construct() /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Container/Container.php:749

PHP Fatal error:  Uncaught ReflectionException: Class config does not exist in /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Container/Container.php:749
Stack trace:
#0 /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Container/Container.php(749): ReflectionClass->__construct('config')
#1 /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Container/Container.php(644): Illuminate\Container\Container->build('config', Array)
#2 /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(709): Illuminate\Container\Container->make('config', Array)
#3 /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php(106): Illuminate\Foundation\Application->make('config', Array)
#4 /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php(263): app('config')
#5 /home/vagrant/Code/demo.local.test/app/Service/HeatTimer/HeatTimer.php(287): config( in /home/vagrant/Code/demo.local.test/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 749
PHP Stack trace:
PHP   1. {main}() /home/vagrant/Code/SourcePhars/phpunit.phar:0
PHP   2. PHPUnit_TextUI_Command::main() /home/vagrant/Code/SourcePhars/phpunit.phar:513
PHP   3. PHPUnit_TextUI_Command->run() phar:///home/vagrant/Code/SourcePhars/phpunit.phar/phpunit/TextUI/Command.php:105

Process finished with exit code 255

After some digging around the tearDown method, I went up the tree and finally found the line that was causing the issue.

A normal test calls parent::tearDown which is \Illuminate\Foundation\Testing\TestCase.php This calls $this->app->flush(); which is in \Illuminate\Foundation\Application.php Which calls it's parent parent::flush which is in Illuminate\Container\Container.php

Which finally runs this code:

    /**
     * Flush the container of all bindings and resolved instances.
     *
     * @return void
     */
    public function flush()
    {
        $this->aliases = [];
        $this->resolved = [];
        $this->bindings = [];
        $this->instances = [];
    }

If I comment out

        $this->instances = [];

Everything works perfectly and there are no more error messages.

Does that help anyone explain these? I'm out of my depth.

Thanks.

ghost commented 7 years ago

Nothing works for me. Magic, magic, magic.

kevnk commented 7 years ago

[Using Laravel] I forgot to add parent::setUp(); in my setUp() method and was getting this error. After adding it, everything worked fine for me.

ghost commented 7 years ago

Damn, this project management looks really idiotic. No one reported that the issue is resolved, but the admin is closing every thread how he like. It's not democratic, it's boorish.

dumindu commented 7 years ago

@blackness90 I faced to a similar issue. Lumen 5.4. unit test but without migrations. using Config::set('key', 'value') was not worked. So I had to use this way.

//test
use Illuminate\Support\Facades\Config;

Config::shouldReceive('get')
                    ->once()
                    ->with('key')
                    ->andReturn('value');
//code
use Illuminate\Support\Facades\Config;

Config::get('key'); 

//instead config('key');
ryanjbonnell commented 5 years ago

I was running into a similar problem when running PHPUnit 7.5 tests with Laravel Lumen 5.4, which was solved by Benjamin Kimball's solution to add or move the setUp() method to the base TestCase class.

The error message I was getting was "ReflectionException: Class config does not exist" when running PHPUnit:

Screen Shot 2019-08-07 at 1 06 40 PM

All of the values in my .env were properly quoted, but the following changes to tests/TestCase.php fixed the issue:

<?php

abstract class TestCase extends Laravel\Lumen\Testing\TestCase
{
    public function createApplication()
    {
        return require __DIR__.'/../bootstrap/app.php';
    }

    public function setUp()
    {
        parent::setUp();

        $this->createApplication();
    }
}