I'm fairly new to unit testing, but I've read pretty much all the documentation on phpunit.de (Up to chapter 10).
It states that testing using databases can be slow, but if setup correctly, it can be just as fast as non-database testing.
As such, I want to test a model in Laravel. I've created a model factory to seed data into the database.
I've also created a basic test.
In PHPUnits documentation, it states that before every test, the setUp()
method is called to setup the test. There's also another static method setUpBeforeClass()
.
I want to seed my database table only once, and use the records within my test. So I used Laravels factory()
function to seed the database from within the setUpBeforeClass()
method.
This is my code:
class CommentTest extends TestCase
{
protected static $blog;
protected static $comments;
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
self::$blog = factory(App\Models\Content\Blog::class)->create();
self::$comments = factory(App\Models\Content\Comment::class, 6)->create();
}
public function testSomething()
{
$this->assertTrue(true);
}
}
However, when I run phpunit
, I get the following error:
Fatal error: Call to a member function make() on a non-object in \vendor\laravel\framework\src\Illuminate\Foundation\helpers.php on line 54
Call Stack:
0.0002 240752 1. {main}() \vendor\phpunit\phpunit\phpunit:0
0.0173 1168632 2. PHPUnit_TextUI_Command::main() \vendor\phpunit\phpunit\phpunit:47
0.0173 1175304 3. PHPUnit_TextUI_Command->run() \vendor\phpunit\phpunit\src\TextUI\Command.php:100
2.9397 5869416 4. PHPUnit_TextUI_TestRunner->doRun() \vendor\phpunit\phpunit\src\TextUI\Command.php:149
2.9447 6077272 5. PHPUnit_Framework_TestSuite->run() \vendor\phpunit\phpunit\src\TextUI\TestRunner.php:440
2.9459 6092880 6. PHPUnit_Framework_TestSuite->run() \vendor\phpunit\phpunit\src\Framework\TestSuite.php:747
2.9555 6096160 7. call_user_func:{\vendor\phpunit\phpunit\src\Framework\TestSuite.php:697}() \vendor\phpunit\phpunit\src\Framework\TestSuite.php:697
2.9555 6096272 8. CommentTest::setUpBeforeClass() \vendor\phpunit\phpunit\src\Framework\TestSuite.php:697
2.9555 6096480 9. factory() \tests\CommentTest.php:18
2.9556 6096656 10. app() \vendor\laravel\framework\src\Illuminate\Foundation\helpers.php:350
If I move the code from setUpBeforeClass()
to setUp()
and run it, it works as expected, but surely this is inefficient as its seeding the database for every test?
My questions:
setUpBeforeClass()
the correct way to do this?factory()
?setUp()
method, are there going to be performance issues?setUpBeforeClass()
or setUp()
methods? In Laravels documentation it shows examples where the seeding is happening in the test itself, but If i'm running 100 tests (for example), is it a good idea to be seeding 100 times?Use Laravel Unit Testing to Avoid Project-Wrecking Mistakes. Pardeep Kumar. 7 Min Read. PHPUnit is one of the most well known and highly optimized unit testing packages of PHP. It is a top choice of many developers for rectifying different developmental loopholes of the application.
PHPUnit is a unit testing framework for the PHP programming language. It is an instance of the xUnit design for unit testing systems that began with SUnit and became popular with JUnit. Even a small software development project usually takes hours of hard work.
To create a new test class, we can either create a new file manually or run the helpful Artisan make:test command provided by Laravel. The most important thing to notice here is the test prefix on the method name. Like the Test suffix for class names, this test prefix tells PHPUnit what methods to run when testing.
Ok, after a bit of investigating (the classes), I've determined that the Laravel application has not yet been created when the static setUpBeforeClass()
method is called.
The Laravel container is created the first time setUp()
is called in \vendor\laravel\framework\src\illuminate\Foundation\Testing\TestCase.php
. That's why it works fine when I move my code to the setUp()
method.
The container is then stored in the $app
property stored in \vendor\laravel\framework\src\illuminate\Foundation\Testing\ApplicationTrait.php
.
I can manually create a container instance by adding this code to the setUpBeforeClass()
method:
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
But this method seems pretty hacky, and I don't like it.
Instead, I moved the seeding code to the setUp
() method, but only seeded the database if the class properties were null. Therefore it only gets seeded on the first call of setUp()
. Any subsequent calls do not get seeded:
class CommentTest extends TestCase
{
use DatabaseMigrations;
protected static $blog;
protected static $comments;
public function setUp()
{
parent::setUp();
$this->runDatabaseMigrations();
if (is_null(self::$blog)) {
self::$blog = factory(App\Models\Content\Blog::class, 1)->create();
self::$comments = factory(App\Models\Content\Comment::class, 6)->create();
}
}
}
In combination with Laravels DatabaseMigrations
trait for testing, This is now the workflow:
DatabaseMigrations
traitsetUp()
method is called for the first time, which seeds the relevant tables with testing datatearDown()
method invoked, instead the DatabaseMigrations
trait simply resets the database, so my test doesn't have to worry about cleaning up the test data.EDIT
In addition, it seems (although I'm not 100%), that if you have a custom setUp()
method, you need to manually call runDatabaseMigrations()
from the overridden setUp()
method:
public function setUp()
{
parent::setUp();
$this->runDatabaseMigrations();
/** Rest of Setup **/
}
runDatabaseMigrations()
doesn't seem to get called automatically if you overload the setUp()
method.
I hope this helps, but if anyone else has a better solution, please feel free to let me know :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With