Search code examples
unit-testingphpunitlaravel-7mockery

How can I write a test in this case?


P.S.: At first I asked how to mock Model::create, but what I wanted to do was to write a test that would correctly go into the catch when an error occurred in the try.

Thanks to @matiaslauriti's comment, I found a better way, so I will change my question from how to mock to how to write tests.

Please see the answer section and comments for details.

・・・・・・・・

I have this code.

class MyModel extends Model
{
    public static function addRecord()
    {
        //...

        try {
            DB::beginTransaction();

            foreach ($rows as $row) {
                self::create([
                    'name'         => $name,
                    'phone'         => '',
                ]);
            }

            DB::commit();
        } catch (\Exception $ex) {
            DB::rollback();

            throw($e);
        }

    }
}

Here is my test code.but The mock returns an error and the test doesn't work.

class MyModelTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->my_model = new MyModel();

    }

    /**
     * @test
     */
    public function addRecord_error()
    {
        $mock = \Mockery::mock(MyModel::class)->makePartial();
        $mock->shouldReceive('create')
      ->once()
          ->andThrow(new \Exception());

        $this->app->bind(MyModel::class, function () use ($mock) {
            return $mock;
        });

     $my_model = app(MyModel::class);

        //run method
        $my_model->addRecord();  

        // ↑↑ This return error with 
              //BadMethodCallException:Static method 
              //Mockery_2_App_Models_MyModel::addRecord() does not exist on this mock object

        //try {
           //$my_model->addRecord();

        //} catch (\Exception $ex) {

          // $this->assertCount(0, MyModel::all());
        //}
    }
}

I would like to write a test where if the try fails, it goes to catch and rollback is called.


Solution

  • Your test code should look like this:

    use Exception;
    use Mockery\Mockery;
    
    class MyModelTest extends TestCase
    {
        /**
         * @test
         */
        public function addRecord_error()
        {
            $mock = Mockery::mock(MyModel::class)->makePartial();
            $mock->shouldReceive('create')
          ->once()
              ->andThrow(new Exception);
    
            $my_model = app(MyModel::class);
    
            try {
                $my_model::addRecord();
            } catch (Exception $exception) {
               $this->assertCount(0, Model::all());
            }
        }
    }
    

    If you are using the model inside itself, you must add the mock to the Service Container before app helper function:

    $this->app->bind(MyModel::class, function () use ($mock) {
        return $mock;
    });
    

    Have in mind that $this->assertCount(0, Model::all()); should be replaced with the Model you want to test if it was rolled back. So replace Model with the desired Model.

    But, that should pass all the time, as you are throwing the error the first time it is run, so you are never adding anything. So assertCount should always pass, and if you remove it or not, is the same.