We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Sharing DI services between application and unit tests

Hi guys -

I'm having some difficulty getting my unit tests (set up the way in the docs) to share the DI services I define in my application. All of my application libraries & components extend the \Phalcon\Mvc\User\Component class and allow me to call services like $this->session->get('var') from within my app libraries. $this->session in this case is a service shared on the dependency injector and defined in /app/config/services.php.

What I want to do is use the same services in my unit tests that my application uses. To do that I've included the same files in my /tests/bootstrap.php file that I did in my /public/index.php file. The problem I'm having is that my application libraries are throwing an error on calls like $this->session because it's not recognizing the service.

However, if I change my app libraries to do this:

$session = \Phalcon\DI::getDefault()->getSession(); $session->get( 'var' );

it works okay.

Is there any way I can maintain the $this->session syntax in my app libraries after bootstrapping the same services in my unit tests? I (a) don't want to redefine the services in my unit tests, and (b) don't want to muddy my app library code with a ton of DI::getDefault()->getService() calls.

I appreciate any help you guys can offer!

So after a lot of deliberation on this, I've come to the conclusion that statically accessing the DI is the easiest way to structure an application. If anyone's interested I can put up a sample application on github with a unit test suite and the main application sharing the same dependency injection services. Basically all of my application libraries extend a base library with a static method getService( $service ) that will return the requested service (via DI::getDefault()->get$service()).

I would definitely be interested in seeing that.

Ok, I'm working on cleaning this up a little bit and I'll post a link here once I do.

It's a pretty nice working boilerplate set up that incorporates what I feel is a really nice and clean application structure for a large REST API application. I'll post more about it when I get it on github, hopefully some time this week but who knows with the holiday :P

Hey Mike, any news?

Yes, so I have the code written (mostly) and up on github/bitbucket if anyone is interested. However, I plan to write a few articles to explain everything.

If you just want to check it out: https://github.com/mikegioia/phalcon-boilerplate https://bitbucket.org/mikegioia/phalcon-boilerplate

There's a quickstart guide in there and the code should be fully working. Once I get a more detailed guide written I'll make a dedicated post in this forum about it.

Your work is awesome.

In your approach you use static getDefault Dependency Injection in almost every place :( This is not the best approach. I personally think that if someone need DI to write unit tests than something is wrong.

To be more constructive I'll also try to grab some pieces from my project and create also some basic starter.

Thanks, Tomasz. I would definitely appreciate any feedback or guidance on the best way to access the dependencies. The main reason I statically reference the DI services is to allow for access to them from any bootstrapped source (i.e. public web, unit tests, CLI, etc). I couldn't figure out a way to access the DI services from my LIbraries, Components, etc. that didn't completely break my unit tests. I can see in the docs that Phalcon suggests attaching the DI services in the setUp() function for each test; however, this seemed inelegant and duplicating the services I register in my main application.

Is there a way to keep all of my services in one place, register them in the Unit Test bootstrap, and then access them in my application code using the magic $this->service? Why is statically accessing the DI in the app code a bad idea?

Mike

Hi

using statically accessing DI is wrong in unit tests because you need to

class UnitTest
{
  public function setUp()
  {
    $di = new DI() // create DI instance
  }

  public function testCalc()
  {
     $calc = new Calc();
     $this->assertEquals(4, $calc->sum(2,2));
  }
}

if class Calc need some some service to do the work it should be injected into __constructor of Calc class. Dependency Injection (injection is the most important word here) only speed this for developer

$di->set(
  'calc', 
  array(
    'className' => 'Calc',
    'arguments' => array(
        array('type'=>'service', 'name' => 'neededService')
    )
  )
);

$di->get('calc')->sum(2,2);

This is production code. In test using DI is IMO wrong because testing simple class you are depends on static or non-static object with Dependency Injection Container. For upper example test for this can look this:

class UnitTest
{
  public function testCalc()
  { 
     $service = new Mock('ServiceName', array('method' => function() { /* code */ }));
     $calc = new Calc($service);
     $this->assertEquals(4, $calc->sum(2,2));
  }
}

This way you not only have full control in your test. No global dependency but also can mock all dependency to your needs. This way you only testing your class (Unit testing) and mock everything else. Another value from this approach is speed. For example you can mock whole database object and run super fast unit test with database usage. This is only for unit tests. If you're doing integrations tests then DI can be helpful to speed up writing tests.

One more thing this approach is really hard to get. If I work on old projects I don't change the code of application but using codeception extension AspectMock. This tool give you ability to test even service which use Active Record objects.

Thanks again, Tomasz. I see your point about not referencing the DI in the unit test cases. That makes total sense and I'll remove that from my unit tests and replace them with mocks or remove it altogether.

However, there's one problem I'm having that still remains unanwered. My application code (libraries, models, components, etc) all use the services I've registered into the dependency injector. For example, say I've set up the URL service, like from the Phalcon docs:

$di->set('url', function(){
    $url = new \Phalcon\Mvc\Url();
    $url->setBaseUri('/tutorial/');
    return $url;
});

Now my application libraries may use this URL service (as well as other services like the session or cache):

class Foo extends \Phalcon\Mvc\User\Component {
    public function bar() {
        return $this->url->get( 'test/test' );
    }
    public static function baz() {
        $url = \Phalcon\Di::getDefault()->getUrl();
        return $url->get( 'test/test' );
}

So my questions are:

(1) What is the correct way to register the application services when running the unit tests? As of now, I am loading them in my unit test bootstrap (like this https://github.com/mikegioia/phalcon-boilerplate/blob/master/tests/bootstrap.php#L25), passing that $di variable into \Phalcon\DI::setDefault( $di ), and then in my setUp method I merely get the default DI and pass it again to the parent setUp.

(2) I arrange most of my application into static classes and therefore need to access the service container statically (like baz() above). The Phalcon docs seem to suggest this (https://docs.phalcon.io/en/latest/reference/di.html#our-approach) is correct but from your previous comments it seems this approach may fall into the "Service Locator" anti-pattern. Is it acceptable to statically reference the services from my application this way? If I cannot, what is the correct way to access the services and will that impact my unit tests?

Thanks again for all of your help. It seems I'm continuing to struggle with the best way to integrate my app and unit tests :(

Mike

1) I personally don't use DI at tests at all, so I can't help you :(

2) About Foo na URL service.

In example It seems url is core dependency for Foo component. In this case you can inject url service into foo service

$di->set('url', function() {});
$di->set('foo', array(
  'className' => 'Foo',
  'arguments' => array(
    array('type'=>'service', 'name' => 'url')
  )
));
class Foo extends \Phalcon\Mvc\User\Component {
    public function __construct($url)
    {
         $this->url = $url;
    }
    public function bar() {
        return $this->url->get( 'test/test' );
    }
    public static function baz() {
        return $this->url->get( 'test/test' );
    }

This is my approach but of course it is more complicated to start than offical way in Phalcon docs. Phalcon docs show most practical way to build RAD application. When you known what you are doing Service Locator is not anti pattern. The only drawback is that it can be use in wrong way in wrong places. I just found than it's better to work without Service Locator in my developers teams. When you work with a lot people with different levels of knowledge it's easier to work without Service Locators. When someone will need to change one service he see all dependency in __constructor or setService($service) methods and have full knowledge about work he need to do.

By the way I don't want to show you that this approach is only correct way and Service Locator is wrong. If Service Locator works for you, than you should stick with it. I only show you another option how you can manage dependency between object without using \Phalcon\DI\InjectionAwareInterface

If you want to see I recently added some code to phalcon incubator

https://github.com/phalcon/incubator/blob/master/Library/Phalcon/Validation/Validator/Db/Uniqueness.php

Here are unit tests in codeceptions:

https://github.com/phalcon/incubator/blob/master/codeception/unit/Phalcon/Validation/Validator/Db/UniquenessTest.php

The tests code is not the best way, It would be better to mock adapter for db connection instead of dbal. Unforunately I'm currently reading and experimetal with Phalcon Db source code and not yet found easy way to mock db pdo connections.

Ah, I think I understand everything you're saying now. Thank you again, Tomasz. The previous comments and the code you just posted is a huge help!

I must admit my knowledge of DI was limited but I think I understand how Phalcon handles it much better than before -- I also see the advantages to managing services via the __constructor and setService.

I haven't heard of Codeception but I'll check that out, it looks cool!

What a great discussion! @Mike, thanks for the sharing, your boilerplate project is a great idea and a great work!

@Tomasz coming from a long completely OO experience ( .NET/Java) your posts makes me feel totally home, i think some problems arise when someone new to Phalcon (like me) follow the official Phalcon user guide that suggest to request DI in the constructor of any custom service... ( https://docs.phalcon.io/en/latest/reference/di.html# - last piece of code before "Our approach") Now i feel more free to apply general OO best practice to php/Phalcon too.