Functional tests on codeception do not work - php

I have the following code:
class FirstCest
{
public function _before(FunctionalTester $I)
{
}
public function _after(FunctionalTester $I)
{
}
public function tryToTest(FunctionalTester $I)
{
$I->amOnPage('/about');
}
}
This is my config:
actor: FunctionalTester
modules:
enabled:
- \Helper\Functional
error_level: "E_ALL"
When I try to run: tests/functional/FirstCest.php:tryToTest I get:
[RuntimeException] Call to undefined method
FunctionalTester::amOnPage

Write your own module or use PhpBrowser.
I see that you already started work on https://github.com/visavi/codeception-phpixie/ .
An obvious thing which is missing is Connector class.
Example: https://github.com/Codeception/Codeception/blob/2.4.5/src/Codeception/Lib/Connector/Guzzle6.php#L183-L217
Connector class must extend BrowserKit\Client class (unless your framework is based on Symfony Http component, in that case look at the code of Symfony module and connector).
and implement doRequest method, which transforms BrowserKit\Request object to an object expected by your framework, invokes framework code and transforms framework response to BrowserKit\Response.
The middle part of doRequest method must be based on the code that you put to index.php of your site.
Ask questions in comments.

Related

Codeception, don't print a specific action to report

In codeception, I want check if an element exist in the page and do another test if the first element exist. I can do that simply :
// $I is a AcceptanceTester Object and extends \Codeception\Actor class
try{
$I->see('.firstElement');
}catch(ElementNotFound $e){
// do some actions
}
// do some anothers actions
But If I do that, in the report file I can see the line "I see '.firstElement'". I don't want see this test in this report.
My question : How can I call a \Codeception\Actor method quietly ? I just want do a simple DOM element html check and not print this action into the generated report
You can create a simple helper module to check elements existence. It can use WebDriver module or PhpBrowser module to elements finding. For example:
class ElementChecker extends \Codeception\Module
{
public function checkExistence($locator)
{
$elements = $this->getModule('WebDriver')->_findElements($locator);
return !empty($elements);
}
},
After it, you should add this helper to your codeception configuration. For example:
actor: SomeTester
modules:
enabled:
# some modules
- ElementChecker
And new methods will be included in the tester class. You can use them:
if ($I->checkExistence('.firstElement')) {
// some code
}
Also, you can read more about helpers in the official documentation

Symfony 4 : Override public services in container

I am migrating our project to Symfony 4. In my test suites, we used PHPUnit for functional tests (I mean, we call endpoints and we check result). Often, we mock services to check different steps.
Since I migrated to Symfony 4, I am facing this issue: Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "my.service" service is already initialized, you cannot replace it.
when we redefine it like this : static::$container->set("my.service", $mock);
Only for tests, how can I fix this issue?
Thank you
Replacing is deprecated since Symfony 3.3. Instead of replacing service you should try using aliases.
http://symfony.com/doc/current/service_container/alias_private.html
Also, you can try this approach:
$this->container->getDefinition('user.user_service')->setSynthetic(true);
before doing $container->set()
Replace Symfony service in tests for php 7.2
Finally, I found a solution. Maybe not the best, but, it's working:
I created another test container class and I override the services property using Reflection:
<?php
namespace My\Bundle\Test;
use Symfony\Bundle\FrameworkBundle\Test\TestContainer as BaseTestContainer;
class TestContainer extends BaseTestContainer
{
private $publicContainer;
public function set($id, $service)
{
$r = new \ReflectionObject($this->publicContainer);
$p = $r->getProperty('services');
$p->setAccessible(true);
$services = $p->getValue($this->publicContainer);
$services[$id] = $service;
$p->setValue($this->publicContainer, $services);
}
public function setPublicContainer($container)
{
$this->publicContainer = $container;
}
Kernel.php :
<?php
namespace App;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function getOriginalContainer()
{
if(!$this->container) {
parent::boot();
}
/** #var Container $container */
return $this->container;
}
public function getContainer()
{
if ($this->environment == 'prod') {
return parent::getContainer();
}
/** #var Container $container */
$container = $this->getOriginalContainer();
$testContainer = $container->get('my.test.service_container');
$testContainer->setPublicContainer($container);
return $testContainer;
}
It's really ugly, but it's working.
I've got a couple of tests like this (the real code performs some actions and returns a result, the test-version just returns false for every answer).
If you create and use a custom config for each environment (eg: a services_test.yaml, or in Symfony4 probably tests/services.yaml), and first have it include dev/services.yaml, but then override the service you want, the last definition will be used.
app/config/services_test.yml:
imports:
- { resource: services.yml }
App\BotDetector\BotDetectable: '#App\BotDetector\BotDetectorNeverBot'
# in the top-level 'live/prod' config this would be
# App\BotDetector\BotDetectable: '#App\BotDetector\BotDetector'
Here, I'm using an Interface as a service-name, but it will do the same with '#service.name' style as well.
As I understood it, it means that class X was already injected(because of some other dependency) somewhere before your code tries to overwrite it with self::$container->set(X:class, $someMock).
If you on Symfony 3.4 and below you can ovverride services in container regardless it privite or public. Only deprication notice will be emmited, with content similar to error message from question.
On Symfony 4.0 error from the question was thown.
But on Symfony 4.1 and above you can lean on special "test" container. To learn how to use it consider follow next links:
https://symfony.com/blog/new-in-symfony-4-1-simpler-service-testing
https://dev.to/nikolastojilj12/symfony-5-mocking-private-autowired-services-in-controller-functional-tests-24j4

How to use Codeception Acceptance Helper?

I'm trying to login to Joomla backend before all tests in my Cest Class.
I'm using the Joomla Browser Module for that:
https://github.com/joomla-projects/joomla-browser
When using it inside the cest class the login gets performed before every test, which is not wanted:
public function _before(AcceptanceTester $I)
{
$I->doAdministratorLogin();
}
When adding it to the Acceptance Helper like this:
namespace Helper;
class Acceptance extends \Codeception\Module
{
public function _beforeSuite($settings = array()) {
$I = $this;
$I->doAdministratorLogin();
}
}
I get
Call to undefined method Helper\Acceptance::doAdministratorLogin()
You have to retrieve JoomlaBrowser module:
$this->getModule('JoomlaBrowser')->doAdministratorLogin();
Also you used _before method in Cest file, but _beforeSuite in Helper file.
WebDriver object is not initialized in _beforeSuite.
Your options are:
move that code to _before
call _initialize method in _beforeSuite
$this->getModule('JoomlaBrowser')->_initialize();
$this->getModule('JoomlaBrowser')->doAdministratorLogin();

How to use custom HandlerWrapper for Monolog in Symfony config.yml?

I would like to create a custom HandlerWrapper to use it with Monolog in my Symfony 2.8 project.
The goal is to use this CustomHandler as a filter that decides wether the wrapped/nested handler is called or not.
==============
UPDATE: The following question is already solved thanks to the answer of #Yonel. However this brought ab a new problem described below.
Extending Monolog\Handler\HandlerWrapper is no problem of course. But I am struggling to us the custom class in the Monolog config within config_prod.yml:
namespace AppBundle\Log;
use Monolog\Handler\HandlerWrapper;
class CustomHandler extends HandlerWrapper {
public function handle(array $record) {
// some custom handling/processing...
if ($this->doSomeCheck())
return $this->handler->handle($record);
else
return false; // Do not call nested handler
}
}
Config:
services:
monolog.custom_handler.service:
class: AppBundle\Log\CustomHandler
monolog:
main:
type: service
id: monolog.custom_handler.service
level: error
handler: someHandler
someHandler:
...
Problem: When running app/console cache:warmup on this config, I get the following error:
[Symfony\Component\Debug\Exception\ContextErrorException]
Catchable Fatal Error: Argument 1 passed to
Monolog\Handler\GroupHandler::__construct() must be of the type array,
none given
Well, I think the source of the problem is obvious: The service definition of monolog.custom_handler.service does not pass any argument to the class. But how can I pass someHandler as argument, as defined in the Monolog config?
==============
EDIT: New Problem
As Yonel describes in his answer, on can use the following config to pass someHandler to custom_handler:
services:
monolog.custom_handler.service:
class: AppBundle\Log\CustomHandler
arguments:
- '#monolog.handler.testHandler'
monolog.test_handler.service:
class: AppBundle\Log\TestHandler
monolog:
# Skip level checking (can be solved by adding a FingersCrossed handler)
main:
type: service
id: monolog.custom_handler.service
testHandler:
type: service
id: monolog.test_handler.service
nested: true
This works: TestHandler is now correctly passed to CustomHandler
However the goal was to let CustomHandler decide, wether TestHandler is called or not. This does not work with this config: TestHandler is called anyway (I assume directly by Monolog, as any other handler). It does not make any difference if TestHandler is passed to CustomHandler, marked as nested or not.
I implemented CustomHandler as HandlerWrapper and TestHandler as AbstractHandler. Both classes directly to a log file (not using Monolog) when their handle method is called. This way I can check if the handlers work as expected (TestHandler should only be called if CustomHandler allows it). This is not the case.
No matter if TestHandler is passed to CustomHandler, if it is marked as nested, if CustomHandler returns true or false in its handle method, etc., the result is always the same: TestHandler is called, no matter what CustomHandler decides.
How to solve this?
Handler implementations:
namespace AppBundle\Log;
use Monolog\Handler\HandlerWrapper;
class CustomHandler extends HandlerWrapper {
public function handle(array $record) {
// some custom handling/processing...
if ($this->doSomeCheck()) {
$this->directlyWriteToFileNotUsingMonolog('CustomHandler: Call TestHandler');
return $this->handler->handle($record);
else {
$this->directlyWriteToFileNotUsingMonolog('CustomHandler: Do NOT call TestHandler');
return false; // Do not call nested handler
}
}
class TestHandler extends AbstractHandler {
public function handle(array $record) {
$this->directlyWriteToFileNotUsingMonolog('TestHandler');
return false;
}
}

Run custom code after Codeception suite has finished

I am aware of the _bootstrap.php file that's used to set up the testing enviroment, etc., but I'm looking for a way to run some code after the entire test suite has finished.
Note that I'm not looking for a way to run code after a single class, i.e. something like _after, but after all classes.
Is there a way to achieve this?
Actually managed to solve this myself, here's how, if anyone is interested.
I created a new helper class inside _support.
<?php
class DataHelper extends \Codeception\Module
{
public function _beforeSuite()
{
// Set up before test suite
}
public function _afterSuite()
{
// Tear down after test suite
}
}
You can then enable this as a module in any suite configuration (the .yml files), like this:
modules:
enabled:
- DataHelper
#Sacha's solution is specially useful if you want to share the same methods accross all suites.
If you're looking for a way to define the methods for a specific suite (or if you want a different method per suite), you can define those methods directly in the suite Helper class.
For instance, if you want to define a _afterSuite method for the Acceptance Suite, just go to support/AcceptanceHelper.php and define those methods there. Eg:
<?php
namespace Codeception\Module;
// here you can define custom actions
// all public methods declared in helper class will be available in $I
class AcceptanceHelper extends \Codeception\Module
{
public function _afterSuite() {
die('everything done');
}
}

Categories