Ad

How Is It Possible To Test Services With Laravel Using PhpUnit?

- 1 answer

I would like to test some of my services, but I can not find any example on Laravel's website: https://laravel.com/docs/5.1/testing

They show how to test simple classes, entities, controllers, but I have no idea how to test services. How is it possible to instantiate a service with complex dependencies?


Example service:

<?php

namespace App\Services;

// Dependencies
use App\Services\FooService;
use App\Services\BarService;

class DemoService {

    private $foo_srv;
    private $bar_srv;

    function __construct(
        FooService $foo_srv,
        BarService $bar_srv
    ) {
        $this->foo_srv = $foo_srv;
        $this->bar_srv = $bar_srv;
    }

    // I would like to test these two functions

    public function demoFunctionOne() {
        // ...
    }

    public function demoFunctionTwo() {
        // ...
    }

}
Ad

Answer

The quickest thought that might come to the mind is to create a copy of those Service classes, but that could grow so big. This is why there's MockObject in PhpUnit.

To achieve this mock and use it as a replacement to the service class you may need to resolve it in Laravel service container.

Here is how it will look:

class DemoServiceTest extends TestCase
{
    // Dependencies
    use App\Services\FooService;
    use App\Services\BarService;
    use App\Services\DemoService;

    public function testDemoFunctionOne()
    {
        $foo_srv = $this->getMockBuilder(FooService::class)
            //->setMethods(['...']) // array of methods to set initially or they return null
            ->disableOriginalConstructor() //disable __construct
            ->getMock();
         /**
          * Set methods and value to return
          **/
        // $foo_srv->expects($this->any())
        //    ->method('myMethod') //method needed by DemoService?
        //    ->will($this->returnValue('some value')); // return value expected

        $bar_srv = $this->getMockBuilder(BarService::class)
//            ->setMethods(['...']) // array of methods to set initially or they return null
            ->disableOriginalConstructor()
            ->getMock();

        /**
         * Set methods and value to return
         **/
        // $bar_srv->expects($this->any())
        //    ->method('myMethod') //method needed by DemoService?
        //    ->will($this->returnValue('some value')); // return value expected

        $demo_service = new DemoService($foo_srv, $bar_srv);
        $result = $demo_service->demoFunctionOne(); //run demo function

        $this->assertNotEmpty($result); //an assertion
    }
}

We are creating new mock for both FooService and BarService classes, and then passing it when instantiating DemoService.

As you can see without uncommenting the commented chunk of code, then we set a return value, otherwise when setMethods is not used, all methods are default to return null.

Let's say you want to resolve these classes in Laravel Service Container for example then you can after creating the mocks, call:

$this->app->instance(FooService::class, $foo_srv);
$this->app->instance(BarService::class, $bar_srv);

Laravel resolves both classes, feeding the mock classes to any caller of the classes So that you just call your class this way:

$demoService = $this->app->make(DemoService::class);

There are lots of things to watch out for when mocking classes for tests, see sources: https://matthiasnoback.nl/2014/07/test-doubles/, https://phpunit.de/manual/6.5/en/test-doubles.html

Ad
source: stackoverflow.com
Ad