Laravel Test Driven Development — reverse unit test

Time:2021-6-27

Negative CRUD Unit Testing in Laravel5

This is a translationhttps://medium.com/@jsdecena/…

AsCRUD Unit Testing in Laravel5In the second part of this article, we will discuss reverse testing in the future.

The fighter forward test we wrote in the last article; Assertion cancreateupdateshowperhapsdelete CarouselModel objects. Now let’s conduct orientation test to see how we can control the above actions if they fail?

Start with the create test

<?php
namespace Tests\Unit\Carousels;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{
    /** @test */
    public function it_should_throw_an_error_when_the_required_columns_are_not_filled()
    {
        $this->expectException(CreateCarouselErrorException::class);
        $carouselRepo = new CarouselRepository(new Carousel);
        $carouselRepo->createCarousel([]);
    }
}

Remember when we created the migration file of carousel, welinkField is set to nullable, andtitleandsrcField is not allowed to be empty.

<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCarouselTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('carousels', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->string('link')->nullable();
            $table->string('src');
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('carousels');
    }
}

So we expect when we try to set it uptitleandsrcWhen null, the database should throw an error, right? The good news is that we caught this error in the repository class.

<?php
namespace App\Shop\Carousels\Repositories;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Exceptions\CreateCarouselErrorException;
use Illuminate\Database\QueryException;
class CarouselRepository
{
    /**
     * CarouselRepository constructor.
     * @param Carousel $carousel
     */
    public function __construct(Carousel $carousel)
    {
        $this->model = $carousel;
    }
    /**
     * @param array $data
     * @return Carousel
     * @throws CreateCarouselErrorException
     */
    public function createCarousel(array $data) : Carousel
    {
        try {
            return $this->model->create($data);
        } catch (QueryException $e) {
            throw new CreateCarouselErrorException($e);
        }
    }
}

In laravel, database errors are thrownQueryExceptionException, so we caught the exception and created a more readable exceptionCreateCarouselErrorException

<?php
namespace App\Shop\Carousels\Exceptions;
class CreateCarouselErrorException extends \Exception
{
}

When these are ready, runphpunitAnd see what happens.

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 993 ms, Memory: 26.00MB
OK (1 test, 1 assertion)

The above result means that we caught the exception correctly.

Then we can catch the exception in the controller and define our own behavior when the exception occurs.

read test

If not foundCarsouelWhat should model objects do?

<?php
namespace Tests\Unit\Carousels;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{
    /** @test */
    public function it_should_throw_not_found_error_exception_when_the_carousel_is_not_found()
    {
        $this->expectException(CarouselNotFoundException::class);
        $carouselRepo = new CarouselRepository(new Carousel);
        $carouselRepo->findCarousel(999);
    }
    /** @test */
    public function it_should_throw_an_error_when_the_required_columns_are_not_filled()
    {
        $this->expectException(CreateCarouselErrorException::class);
        $carouselRepo = new CarouselRepository(new Carousel);
        $carouselRepo->createCarousel([]);
    }
}

Go back to the repository class and see ourfindCarousel()method

<?php
namespace App\Shop\Carousels\Repositories;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Exceptions\CarouselNotFoundException;
use App\Shop\Carousels\Exceptions\CreateCarouselErrorException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
class CarouselRepository
{
   protected $model;
  
    /**
     * CarouselRepository constructor.
     * @param Carousel $carousel
     */
    public function __construct(Carousel $carousel)
    {
        $this->model = $carousel;
    }
    ...
      
    /**
     * @param int $id
     * @return Carousel
     * @throws CarouselNotFoundException
     */
    public function findCarousel(int $id) : Carousel
    {
        try {
            return $this->model->findOrFail($id);
        } catch (ModelNotFoundException $e) {
            throw new CarouselNotFoundException($e);
        }
    }
    
     ...
}

stayfindCarousel()Method, we capture laravel’sfindOrFail()Thrown by default when the model cannot be foundModelNotFoundException

Now run it againphpunit

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 936 ms, Memory: 26.00MB
OK (1 test, 1 assertion)

It looks good, so what should we do if we can’t update?

update test

<?php
namespace Tests\Unit\Carousels;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{
    /** @test */
    public function it_should_throw_update_error_exception_when_the_carousel_has_failed_to_update()
    {
        $this->expectException(UpdateCarouselErrorException::class);
        $carousel = factory(Carousel::class)->create();
        $carouselRepo = new CarouselRepository($carousel);
        $data = ['title' => null];
        $carouselRepo->updateCarousel($data);
    }  
  
    /** @test */
    public function it_should_throw_not_found_error_exception_when_the_carousel_is_not_found()
    {
        $this->expectException(CarouselNotFoundException::class);
        $carouselRepo = new CarouselRepository(new Carousel);
        $carouselRepo->findCarousel(999);
    }
    /** @test */
    public function it_should_throw_an_error_when_the_required_columns_are_not_filled()
    {
        $this->expectException(CreateCarouselErrorException::class);
        $carouselRepo = new CarouselRepository(new Carousel);
        $carouselRepo->createCarousel([]);
    }

As you can see, in the above test program, we intentionallytitleThe field is set tonull,Because in the last testtitleSet to null when creatingCarouselAn error is thrown when the error occurs. So let’s assume that the title in the database record already has a value.

Note: when you break an exception in a test program, you should put the statement asserting the exception at the top of the test method

Take a look at theupdateCarousel()method

<?php
namespace App\Shop\Carousels\Repositories;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Exceptions\CarouselNotFoundException;
use App\Shop\Carousels\Exceptions\CreateCarouselErrorException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
class CarouselRepository
{
   protected $model;
  
    /**
     * CarouselRepository constructor.
     * @param Carousel $carousel
     */
    public function __construct(Carousel $carousel)
    {
        $this->model = $carousel;
    }
    ...
    
     /**
     * @param array $data
     * @return bool
     * @throws UpdateCarouselErrorException
     */
    public function updateCarousel(array $data) : bool
    {
        try {
            return $this->model->update($data);
        } catch (QueryException $e) {
            throw new UpdateCarouselErrorException($e);
        }
    }
    ...
}

functionphpunit

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 969 ms, Memory: 26.00MB
OK (1 test, 1 assertion)

Very good, big brother

delete test

Next updeleteBut we have todeleteCarousel()The return value type of the method is declared fromboolChange to?boolIt means it can returnbooleanperhapsnull

Note: you must be running in an environment above php7.1 to apply the above featurehttp://php.net/manual/en/migr…

<?php
namespace App\Shop\Carousels\Repositories;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Exceptions\CarouselNotFoundException;
use App\Shop\Carousels\Exceptions\CreateCarouselErrorException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
class CarouselRepository
{
   protected $model;
  
    /**
     * CarouselRepository constructor.
     * @param Carousel $carousel
     */
    public function __construct(Carousel $carousel)
    {
        $this->model = $carousel;
    }
    ...
    
    /**
    * @return bool
    */
    public function deleteCarousel() : ?bool
    {
        return $this->model->delete();
    }

Then there is the test program

<?php
namespace Tests\Unit\Carousels;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{
    /** @test */
    public function it_returns_null_when_deleting_a_non_existing_carousel()
    {
        $carouselRepo = new CarouselRepository(new Carousel);
        $delete = $carouselRepo->deleteCarousel();
        $this->assertNull($delete);
    }
    /** @test */
    public function it_should_throw_update_error_exception_when_the_carousel_has_failed_to_update()
    {
        $this->expectException(UpdateCarouselErrorException::class);
        $carousel = factory(Carousel::class)->create();
        $carouselRepo = new CarouselRepository($carousel);
        $data = ['title' => null];
        $carouselRepo->updateCarousel($data);
    }  
  
    /** @test */
    public function it_should_throw_not_found_error_exception_when_the_carousel_is_not_found()
    {
        $this->expectException(CarouselNotFoundException::class);
        $carouselRepo = new CarouselRepository(new Carousel);
        $carouselRepo->findCarousel(999);
    }
    /** @test */
    public function it_should_throw_an_error_when_the_required_columns_are_not_filled()
    {
        $this->expectException(CreateCarouselErrorException::class);
        $carouselRepo = new CarouselRepository(new Carousel);
        $carouselRepo->createCarousel([]);
    }
}

functionphpunitThe results are as follows

➜  git: phpunit --filter=CarouselUnitTest::it_error_when_deleting_a_non_existing_carousel
PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 938 ms, Memory: 26.00MB
OK (1 test, 1 assertion)

This is the end of the process of how to implement crud reverse unit testing.

Laravel Test Driven Development -- reverse unit test