How to run Codeception functional tests against a custom frameworkless web application? (that implements PSR-15 RequestHandlerInterface) - codeception

Assume the following simple web app:
<?php
// src/App/App.php
namespace Practice\Sources\App;
use Closure;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface;
class App implements RequestHandlerInterface
{
private Closure $requestProvider;
private ResponseFactoryInterface $responseFactory;
private SapiEmitter $responseEmitter;
public function __construct(ResponseFactoryInterface $responseFactory)
{
$this->requestProvider = fn() => ServerRequestFactory::fromGlobals();
$this->responseFactory = $responseFactory;
$this->responseEmitter = new SapiEmitter();
}
public function run(): void
{
$request = ($this->requestProvider)();
$response = $this->handle($request);
$this->responseEmitter->emit($response);
}
public function handle(RequestInterface $request): ResponseInterface
{
$response = $this->responseFactory->createResponse();
$response->getBody()->write('hello world');
return $response;
}
}
One can easily run it by placing the following code in their web front-controller (e.g. public_html/index.php):
<?php
// web-root/front-controller.php
use Laminas\Diactoros\ResponseFactory;
use Practice\Sources\App\App;
require_once __DIR__.'/../vendor/autoload.php';
$responseFactory = new ResponseFactory();
$app = new App($responseFactory);
$app->run();
Now, I'd like to run Codeception feature tests against it, written in Gherkin. Consider the following simple test:
# tests/Feature/run.feature
# (also symlink'ed from tests/Codeception/tests/acceptance/ )
Feature: run app
Scenario: I run the app
When I am on page '/'
Then I see 'hello world'
To run acceptance tests against it, I have to provide my steps implementation. I'll reuse standard steps for that, provided by the Codeception:
<?php
// tests/Codeception/tests/_support/FeatureTester.php
namespace Practice\Tests\Codeception;
use Codeception\Actor;
abstract class FeatureTester extends Actor
{
// use _generated\AcceptanceTesterActions;
// use _generated\FunctionalTesterActions;
/**
* #When /^I am on page \'([^\']*)\'$/
*/
public function iAmOnPage($page)
{
$this->amOnPage($page);
}
/**
* #Then /^I see \'([^\']*)\'$/
*/
public function iSee($what)
{
$this->see($what);
}
}
<?php
// tests/Codeception/tests/_support/AcceptanceTester.php
namespace Practice\Tests\Codeception;
class AcceptanceTester extends FeatureTester
{
use _generated\AcceptanceTesterActions;
}
# tests/Codeception/codeception.yml
namespace: Practice\Tests\Codeception
paths:
tests: tests
output: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
actor_suffix: Tester
extensions:
enabled:
- Codeception\Extension\RunFailed
# tests/Codeception/tests/acceptance.suite.yml
actor: AcceptanceTester
modules:
enabled:
- PhpBrowser:
url: http://practice.local
gherkin:
contexts:
default:
- \Practice\Tests\Codeception\AcceptanceTester
But now, I want to use same tests code to run these same tests as functional tests using Codeception. For this, I have to enable the module that implements these same steps in functional way. Which one do I use? Codeception provides several, but they're for 3rd party frameworks, e.g. Laravel, Yii2, Symphony etc. What do I do for such a simple app that doesn't use any 3rd party framework?
Here's what I've managed to do. I've created my own \Codeception\Lib\InnerBrowser implementation that inherits from \Codeception\Module\PhpBrowser provided by Codeception, in which I substitute the web client that Codeception uses (it uses Guzzle) with my own implementation (which also inherits from the Guzzle client) that doesn't perform any web requests but requests my app instead:
# tests/Codeception/tests/functional.suite.yml
actor: FunctionalTester
modules:
enabled:
- \Practice\Tests\Codeception\Helper\CustomInnerBrowser:
url: http://practice.local
gherkin:
contexts:
default:
- \Practice\Tests\Codeception\FunctionalTester
<?php
// tests/Codeception/tests/_support/FunctionalTester.php
namespace Practice\Tests\Codeception;
class FunctionalTester extends FeatureTester
{
use _generated\FunctionalTesterActions;
}
In order for this to work, I have to make my app return Guzzle Responses (which implement PSR's ResponseInterface as well) - because PhpBrowser expects its web client to return them - which is why I had to make the ResponseFactory a constructor parameter to be able to substitute it in tests.
<?php
// tests/Codeception/tests/_support/Helper/CustomInnerBrowser.php
namespace Practice\Tests\Codeception\Helper;
use Codeception\Module\PhpBrowser;
use Http\Factory\Guzzle\ResponseFactory;
use Practice\Sources\App\App;
class CustomInnerBrowser extends PhpBrowser
{
private App $app;
public function __construct(...$args)
{
parent::__construct(...$args);
$responseFactory = new ResponseFactory();
$this->app = new App($responseFactory);
}
public function _prepareSession(): void
{
parent::_prepareSession();
$this->guzzle = new CustomInnerBrowserClient($this->guzzle->getConfig(), $this->app);
$this->client->setClient($this->guzzle);
}
}
<?php
// tests/Codeception/tests/_support/Helper/CustomInnerBrowserClient.php
namespace Practice\Tests\Codeception\Helper;
use GuzzleHttp\Client as GuzzleClient;
use Practice\Sources\App\App;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class CustomInnerBrowserClient extends GuzzleClient
{
private App $app;
public function __construct(array $config, App $app)
{
parent::__construct($config);
$this->app = $app;
}
public function send(RequestInterface $request, array $options = []): ResponseInterface
{
return $this->app->handle($request);
}
}
In such a configuration, everything seems to work fine.
But there's a problem. Notice the App::handle() signature:
public function handle(RequestInterface $request): ResponseInterface
- it differs from the one that it implements, which is declared in RequestHandlerInterface:
public function handle(ServerRequestInterface $request): ResponseInterface;
Technically, it's completely legal because it doesn't break the parameter contravariance required by the Liskov Substitution Principle.
The problem that I've faced is that PhpBrowser assumes that it sends a (client-side) RequestInterfaces (over the network) but my app requires a (server-side) ServerRequestInterface instead, to be able to access parameters that are set on the server side such as ServerRequestInterface::getParsedBody(), session etc.
How do I workaround this? Framework modules provided by Codeception already do this somehow... And BTW, haven't Codeception (or someone else) provided yet an easy way to run functional tests against custom code?
Here's composer.json BTW:
{
"require": {
"php": "~7.4",
"laminas/laminas-diactoros": "^2.5",
"laminas/laminas-httphandlerrunner": "^1.3"
},
"require-dev": {
"codeception/codeception": "^4.1",
"codeception/module-phpbrowser": "^1.0.0",
"http-interop/http-factory-guzzle": "^1.0"
},
"autoload": {
"psr-4": {
"Practice\\Sources\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Practice\\Tests\\Unit\\": "tests/Unit/",
"Practice\\Tests\\Support\\": "tests/Support/",
"Practice\\Tests\\Codeception\\": "tests/Codeception/tests/_support/",
"Practice\\Tests\\Codeception\\_generated\\": "tests/Codeception/tests/_support/_generated/",
"Practice\\Tests\\Codeception\\Helper\\": "tests/Codeception/tests/_support/Helper/"
}
},
"scripts": {
"test-feature": "codecept run --config tests/Codeception/codeception.yml"
}
}

Related

Phalcon Controller $this->session and Phalcon\Session\Manager()

I'm using Phalcon v.4 and I have seen that are two ways to create the session inside a controller:
class PostController extends Controller
{
public function postAction(): Response
{
$session = new Phalcon\Session\Manager()
}
}
or
class PostController extends Controller
{
public function postAction(): Response
{
$this->session;
}
}
I have seen that the methods are the same, but I'm not able to understand the different and which is better to use.
if you created your project using phalcon's cli devtools then the session service would be created by default in app/config/services.php
that being said in your controller when you access the instance's property session aka $this->session this would look for a service called session and by default it would setup session using file adapter and starts it and $this->session would return an instance of Phalcon\Session\Manager

Quarkus Vert.x Example

I want to test Quarkus and the native image for Docker with an existing project written in Kotlin and using Vert.x verticles.
Can you point me to an example on how to deploy verticles using Quarkus?
My dependencies are vertx-sockjs-service-proxy and vertx-lang-kotlin.
I found some examples in the Vert.x extension tests but I cannot find how to deploy my verticles at server startup.
#Inject
EventBus eventBus;
#Route(path = "/hello-event-bus", methods = GET)
void helloEventBus (RoutingExchange exchange){
eventBus.send("hello", exchange.getParam("name").orElse("missing"), ar -> {
if (ar.succeeded()) {
exchange.ok(ar.result().body().toString());
} else {
exchange.serverError().end(ar.cause().getMessage());
}
});
}
You can use verticle as follows:
#Inject Vertx vertx;
void onStart(#Observes StartupEvent ev) {
vertx.deploy(new MyVerticleA());
vertx.deploy(new MyVerticleB());
}

Codeception 2.2 api tests ZF2 and PhpBrowser module together

Codeception API tester require PhpBrowser module and I want to use ZF2 module because I need retrieve some services from ServiceManager.
After update Codeception to 2.2 it throws this exception:
[Codeception\Exception\ModuleConflictException] ZF2 module conflicts
with PhpBrowser
Is there any way to enable ZF2 and PhpBrowser together in Codeception 2.2?
If you have a good reason to use ZF2 in the same suite as PhpBrowser,
you can create your own helper class and load ZF2 module as a dependency.
Configuration:
modules:
enabled:
- PhpBrowser:
url: http://localhost/
- \Helper\Zf2Helper:
depends: ZF2
Code tests/_support/Helper/Zf2Helper.php:
<?php
namespace Helper;
class Zf2Helper extends \Codeception\Module
{
private $zf2;
public function _inject(\Codeception\Module\ZF2 $zf2)
{
$this->zf2 = $zf2;
}
public function doSomethingWithZf2
{
$this->zf2->doSomething();
}
}
Update:
Since Codeception 2.2.2 release it is possible to load services part of ZF2 which enables grabServiceFromContainer method.
Configuration:
modules:
enabled:
- PhpBrowser:
url: http://localhost/
- ZF2
part: services
Thank your for your answer.
Working code with some improvements:
<?php
namespace Helper;
use Codeception\Lib\Interfaces\DependsOnModule;
class Zf2Helper extends \Codeception\Module implements DependsOnModule
{
protected $dependencyMessage = <<<EOF
Example configuring ZF2Helper as proxy for ZF2 module method grabServiceFromContainer.
--
modules:
enabled:
- \Helper\ZF2Helper:
depends: ZF2
--
EOF;
private $zf2;
public function _inject(\Codeception\Module\ZF2 $zf2)
{
$this->zf2 = $zf2;
}
public function _depends()
{
return ['Codeception\Lib\ZF2' => $this->dependencyMessage];
}
public function grabServiceFromContainer($service)
{
return $this->zf2->grabServiceFromContainer($service);
}
}

How to run XUnit test on both self-host & production webapi service?

I'd like to write a test for my ASP.NET WebApi service and run it against a self-hosted service and the live web hosted service. I imagine that this can be done with a test fixture, but I'm not sure how to set it up. Does anyone know of an example of using a configurable test fixture so that you can pass a parameter to Xunit to choose a self hosted fixture or a web hosted fixture?
Here is how it works with latest xUnit 2.0 beta.
Create a fixture:
public class SelfHostFixture : IDisposable {
public static string HostBaseAddress { get; private set; }
HttpSelfHostServer server;
HttpSelfHostConfiguration config;
static SelfHostFixture() {
HostBaseAddress = ConfigurationManager.AppSettings["HostBaseAddress"]; // HttpClient in your tests will need to use same base address
if (!HostBaseAddress.EndsWith("/"))
HostBaseAddress += "/";
}
public SelfHostFixture() {
if (/*your condition to check if running against live*/) {
config = new HttpSelfHostConfiguration(HostBaseAddress);
WebApiConfig.Register(config); // init your web api application
var server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
}
}
public void Dispose() {
if (server != null) {
server.CloseAsync().Wait();
server.Dispose();
server = null;
config.Dispose();
config = null;
}
}
}
Then define a collection that will use that fixture. Collections are the new concept to group tests in xUnit 2.
[CollectionDefinition("SelfHostCollection")]
public class SelfHostCollection : ICollectionFixture<SelfHostFixture> {}
It serves as just a marker so has no implementation.
Now, mark tests that rely on your host to be in that collection:
[Collection("SelfHostCollection")]
public class MyController1Test {}
[Collection("SelfHostCollection")]
public class MyController4Test {}
The runner will create a single instance of your fixture when running any test from within MyController1Test and MyController4Test ensuring that your server is initiated only once per collection.
I would recommend to use the In-Memory Server for testing your controllers, so you don't need to spin up a self-host in your unit tests.
http://blogs.msdn.com/b/youssefm/archive/2013/01/28/writing-tests-for-an-asp-net-webapi-service.aspx

Doctrine2: testing repository classes with YAML config

I have YAML config for my symfony2 project using Doctrine2. I'm not understanding how exactly to adapt the cookbook entry to a YAML setup.
My doctrine mapping is at /path/to/my/bundle/Resources/config/doctrine/IpRange.orm.yml
When running PHPUnit, I get the error:
Doctrine\ORM\Mapping\MappingException: No mapping file found named 'Yitznewton.FreermsBundle.Entity.IpRange.orm.yml' for class 'Yitznewton\FreermsBundle\Entity\IpRange'.
Sounds like I need to configure the test rig to use the symfony file naming conventions, but I don't know how to do that.
Using symfony-standard.git checked out to v2.0.7
Here's my test:
<?php
namespace Yitznewton\FreermsBundle\Tests\Utility;
use Doctrine\Tests\OrmTestCase;
use Doctrine\ORM\Mapping\Driver\DriverChain;
use Doctrine\ORM\Mapping\Driver\YamlDriver;
use Yitznewton\FreermsBundle\Entity\IpRange;
use Yitznewton\FreermsBundle\Entity\IpRangeRepository;
class IpRangeRepositoryTest extends OrmTestCase
{
private $_em;
protected function setup()
{
// FIXME: make this path relative
$metadataDriver = new YamlDriver('/var/www/symfony_2/src/Yitznewton/FreermsBundle/Resources/config/doctrine');
$metadataDriver->setFileExtension('.orm.yml');
$this->_em = $this->_getTestEntityManager();
$this->_em->getConfiguration()
->setMetadataDriverImpl($metadataDriver);
$this->_em->getConfiguration()->setEntityNamespaces(array(
'FreermsBundle' => 'Yitznewton\\FreermsBundle\\Entity'));
}
protected function getRepository()
{
return $this->_em->getRepository('FreermsBundle:IpRange');
}
public function testFindIntersecting_RangeWithin_ReturnsIpRange()
{
$ipRange = new IpRange();
$ipRange->setStartIp('192.150.1.1');
$ipRange->setEndIp('192.160.1.1');
$this->assertEquals(1, count($this->getRepository()
->findIntersecting($ipRange)),
'some message');
}
Looking again at the symfony docs, it seems that integration testing with a live test database is preferred to unit testing for repository classes. That is, there is no support for stubbing EntityManagers.