Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

devmain's avatar

Unit test query builder

How do I create unit test for a method that makes query with query builder? Example:

public function getStudents(){

$students = DB::table('students') ->join('contacts', 'students.id', '=', 'contacts.student_id') ->join('course_choice', 'students.id', '=', 'course_choice.student_id') ->join('courses', 'students.id', '=', 'course_choice.student_id') ->select('students.id','students.name','courses.course_name','contacts.phone','contacts.email') ->get();

}

0 likes
8 replies
Tray2's avatar

You don't really test the query itself, you test the result of the query. I would use a feature test to do that, something like this

public function it_shows_an_author(): void
    {
        $author = Author::factory()->create([
            'first_name' => 'Robert',
            'last_name' => 'Jordan'
        ]);

        $this->get(action([AuthorsController::class, 'show'], $author->id))
            ->assertStatus(200)
            ->assertSee('Jordan, Robert');
    }
2 likes
devmain's avatar

@Tray2 As I understand it, you create an author and then search for him? But doesn't that dirty the database?

1 like
Tray2's avatar
Tray2
Best Answer
Level 73

@thiagoinnfo No since the database should empty when you start the test and empty when you finish the test. In a test you need to know exactly what is inside the database, thus the creation of the author. You should use a trait to make sure that you refresh your database between every test, and you should either use an in memory SQLite database or a dedicated test database. The complete test class for the Author show action in my case looks like this

class ShowTest extends TestCase
{
    use RefreshDatabase;
    /**
    * @test
    */
    public function it_shows_an_author(): void
    {
        $author = Author::factory()->create([
            'first_name' => 'Robert',
            'last_name' => 'Jordan'
        ]);

        $this->get(action([AuthorsController::class, 'show'], $author->id))
            ->assertStatus(200)
            ->assertSee('Jordan, Robert');
    }

    /**
    * @test
    */
    public function it_shows_the_books_written_by_the_author(): void
    {
        $author = Author::factory(['first_name' => 'Robert', 'last_name' => 'Jordan'])
            ->has(Book::factory(['title' => 'The Eye Of The World']))
            ->create();

        $this->get(action([AuthorsController::class, 'show'], $author->id))
            ->assertStatus(200)
            ->assertSee('Jordan, Robert')
            ->assertSee('The Eye Of The World');
    }
}

The refreshDatabase tells phpunit that it should refresh the database between each test.

You use the phpunit.xml file to tell phpunit which database it should use.

In my case I tell it to use one called mysql_test

Depending on your version of Laravel the phpunit.vml uses different syntax, In my projects it uses the <server name> tag to define the laravel settings from the env file, in later versions of Laravel it uses the <env> tag, so change the examples below accordingly.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="DB_CONNECTION" value="mysql_test"/>
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

that one is also defined in my .env file

DB_CONNECTION_TEST=mysql
DB_DATABASE_TEST=mb2_test

If you want to use an in memory database your phpunit file might look something like this

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" stopOnFailure="true">
  <coverage processUncoveredFiles="true">
    <include>
      <directory suffix=".php">./app</directory>
    </include>
  </coverage>
  <testsuites>
    <testsuite name="Unit">
      <directory suffix="Test.php">./tests/Unit</directory>
    </testsuite>
    <testsuite name="Feature">
      <directory suffix="Test.php">./tests/Feature</directory>
    </testsuite>
  </testsuites>
  <php>
    <server name="APP_ENV" value="testing"/>
    <server name="BCRYPT_ROUNDS" value="4"/>
    <server name="CACHE_DRIVER" value="array"/>
    <server name="DB_CONNECTION" value="sqlite"/>
    <server name="DB_DATABASE" value=":memory:"/>
    <server name="MAIL_MAILER" value="array"/>
    <server name="QUEUE_CONNECTION" value="sync"/>
    <server name="SESSION_DRIVER" value="array"/>
  </php>
</phpunit>
1 like
vincent15000's avatar

@Tray2 What is the difference between .env.testing and .env with DB_CONNECTION_TEST and DB_DATABASE_TEST ?

Tray2's avatar

@vincent15000 The first one tells Laravel that it is an mysql database and the other which database to use.

1 like
devmain's avatar

@Tray2

Thanks for the clarifications. Which is better to use for sqlite testing or mysql dedicated to testing? If I have another bank dedicated to tests, I have to control that migrations and seeders are executed on both to be the same in structures, correct? Do you think it's better to create my tests based on the seeders created, making this data fixed in the test bench or do all tests start with the clean bench and create things in it?

Tray2's avatar

@thiagoinnfo It depends, I usually try to use SQLite since it's an in memory database, thus making it faster. There are however some limitations in a SQLite database vs a MySQL database. So as long as you don't use MySQL specific functionality you should stick with SQLite. If you later on in your project start using such functionality it's super simple to move to MySQL.

1 like

Please or to participate in this conversation.