'Cakephp 3 - behavior not loaded during controller unit test
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
Solution 1:[1]
A new global event manager per test
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.
Use Application::bootstrap() in newer CakePHP Versions
As 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.
Add global listeners afterwards as a workaround
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);
}
}
Your snippet doesn't add any listeners
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.initializeEventevent in your app, at least that's what yourInitializeEventListenercode suggests.Instantiating a table class will cause the
Model.initializeevent 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 nextget()call.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
