I have a behavior that I add to all models by initialize event listener.
// src/Event/InitializeEventListener.php
...
class InitializeEventListener implements EventListenerInterface
{
public function implementedEvents()
{
return [
'Model.initialize' => 'initializeEvent'
];
}
public function initializeEvent(Event $event, $data = [], $options = [])
{
$event->subject()->addBehavior('MyBehavior');
}
}
// bootstrap.php
$ieListener = new InitializeEventListener();
EventManager::instance()->on($ieListener);
I have UsersController
and index
action. If I put debug($this->Users->behaviors()->loaded());die;
I can see the default Timestamp
and the loaded MyBehavior
. So far all working fine and I can use MyBehavior
's function in index action. This is when opening the page in the browser.
Now, I have this test function
// tests/TestCase/Controller/UsersControllerTest.php
public function testIndex()
{
$this->get('/users/index');
$this->assertResponseSuccess();
}
However, when running the test the MyBehavior
is not being loaded(it is NOT in the loaded behavior list, and hence tyring to use it's function gives unknown method error). I tried adding this into the UsersController's testcase
public function setUp()
{
parent::setUp();
$this->Users = TableRegistry::get('Users');
$eventList = new EventList();
$eventList->add(new Event('Users.initializeEvent'));
$this->Users->eventManager()->setEventList($eventList);
}
but again MyBehavior
is not loaded.
Thanks
The problem is that the CakePHP test case set a new global event manager instance on setup:
public function setUp()
{
// ...
EventManager::instance(new EventManager());
}
https://github.com/cakephp/cakephp/blob/3.2.12/src/TestSuite/TestCase.php#L106
So everything added to the global event manager before that point is going to be lost.
This is being done in order to avoid state. If it wouldn't be done, then possible listeners added to the global manager by your tested code in test method A, would still be present in test method B, which could of course cause problems for the code tested there, like listeners stacking up, causing them to be invoked multiple times, listeners being invoked that normally wouldn't be invoked, etc.
Application::bootstrap()
in newer CakePHP VersionsAs of CakePHP 3.3 you can use the bootstrap()
method in your application's Application
class, unlike the config/bootstrap.php
file, which is only being included once, that method is being invoked for every integration test request.
This way your global events are being added after the test case assigns a new clean event manager instance.
In version before CakePHP 3.3, whenever I need gloabl events, I store them in a configuration value and apply them at setup time in the test cases, something along the lines of
boostrap
$globalListeners = [
new SomeListener(),
new AnotherListener(),
];
Configure::write('App.globalListeners', $globalListeners);
foreach ($globalListeners as $listener) {
EventManager::instance()->on($listener);
}
test case base class
public function setUp()
{
parent::setUp();
foreach (Configure::read('App.globalListeners') as $listener) {
EventManager::instance()->on($listener);
}
}
That being said, the problem with your setup code snippet is that
It has nothing to with listening to events, but with tracking dispatched events. Also there probably is no Users.initializeEvent
event in your app, at least that's what your InitializeEventListener
code suggests.
Instantiating a table class will cause the Model.initialize
event to be triggered, so even if you would have properly added your listener afterwards, it would never get triggered unless you would have cleared the table registry, so that the users table would be newly constructed on the next get()
call.