Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is it possible to test services with Laravel using PhpUnit?

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() {
        // ...
    }

}
like image 886
Iter Ator Avatar asked Oct 15 '19 10:10

Iter Ator


People also ask

What is PHPUnit testing?

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.

What is PHPUnit Laravel?

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.

What is the purpose of PHPUnit?

PHPUnit is a framework independent library for unit testing PHP. Unit testing is a method by which small units of code are tested against expected results. Traditional testing tests an app as a whole meaning that individual components rarely get tested alone.


1 Answers

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

like image 138
Oluwatobi Samuel Omisakin Avatar answered Oct 19 '22 03:10

Oluwatobi Samuel Omisakin