Is it possible to have Cest files that extend a parent class and make use of a common test "login" that other tests depend on using the #depends
So my Cest file looks similar to the one found in this post which explains how to login and re-use the cookie in another test - https://stackoverflow.com/a/25547268/682754
I've got this common class but the test in the child class doesn't run and outputs...This test depends on 'commonCest::login' to pass.
<?php
class commonCest {
const COOKIE_NAME = 'PHPSESSID';
protected $cookie;
/**
* Most tests will need to depend on this function as it logs the user in and stores the session cookie, use the
* "#depends" phpdoc comment
*
* The cookie in then re-used by calling the following in tests:
*
* $I->setCookie(self::COOKIE_NAME, $this->cookie);
*
* #param \AcceptanceTester $I
*/
public function login(AcceptanceTester $I) {
$I->wantTo('Login');
$I->amOnPage('/login');
$I->fillField(array('name' => 'username'), 'aaaaaa');
$I->fillField(array('name' => 'password'), 'abcdef');
$I->click('.form-actions button');
$I->seeElement('.username');
$this->cookie = $I->grabCookie(self::COOKIE_NAME);
}
}
<?php
use \AcceptanceTester;
class rolesCest extends commonCest
{
/**
* #depends login (This doesn't work)
* #depends commonCest:login (This doesn't work)
* #depends commonCest::login (This doesn't work)
*/
public function listUsers(AcceptanceTester $I)
{
// tests
}
?>
Simply don't have commonCest under your cest directory. Login will then be run for every case of a class extending commonCest in your cest directory as it exists in all of them. You however shouldn't use #depends for this. Rather login should be in your actor or a helper, and that should be called from _before in your parent class.
Or just use stepobjects https://codeception.com/docs/06-ReusingTestCode#stepobjects and call your needed functions from _before
Related
I am working on Laravel 5.7 and i'd like to ask you a question regarding the PHPUnit testing.
I have a test class, let's say ProductControllerTest.php, with two methods testProductSoftDelete() and testProductPermanentlyDelete(). I want to use the annotation #depends in the testProductPermanentlyDelete() in order to soft-delete first a product and then get the product id and proceed to permanently deletion test. The problem here is that the DatabaseTransaction trait runs the transactions in every test (method) execution. I need to start the transaction before all the tests of my ProductControllerTest class and then rollback the transaction at the end of all tests. Do you have any ideas? From what i have searched from the web nothing worked properly.
public function testProductSoftDelete()
{
some code
return $product_id;
}
/**
* #depends testProductSoftDelete
*/
public function testProductPermanentlyDelete($product_id)
{
code to test permanently deletion of the product with id $product_id.
There is a business logic behind that needs to soft delete first a
product before you permanently delete it.
}
Does the following make sense?
namespace Tests\App\Controllers\Product;
use Tests\DatabaseTestCase;
use Tests\TestRequestsTrait;
/**
* #group Coverage
* #group App.Controllers
* #group App.Controllers.Product
*
* Class ProductControllerTest
*
* #package Tests\App\Controllers\Product
*/
class ProductControllerTest extends DatabaseTestCase
{
use TestRequestsTrait;
public function testSoftDelete()
{
$response = $this->doProductSoftDelete('9171448');
$response
->assertStatus(200)
->assertSeeText('Product sof-deleted successfully');
}
public function testUnlink()
{
$this->doProductSoftDelete('9171448');
$response = $this->actingAsSuperAdmin()->delete('/pages/admin/management/product/unlink/9171448');
$response
->assertStatus(200)
->assertSeeText('Product unlinked successfully');
}
}
namespace Tests;
trait TestRequestsTrait
{
/**
* Returns the response
*
* #param $product_id
* #return \Illuminate\Foundation\Testing\TestResponse
*/
protected function doProductSoftDelete($product_id)
{
$response = $this->actingAsSuperAdmin()->delete('/pages/admin/management/product/soft-delete/'.$product_id);
return $response;
}
}
namespace Tests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
abstract class DatabaseTestCase extends TestCase
{
use CreatesApplication;
use DatabaseTransactions;
}
Create a separate function to execute the same behavior twice:
public function testProductSoftDelete()
{
doSoftDelete();
}
public function testProductPermanentlyDelete()
{
doSoftDelete();
doPermanentDelete();
}
Your case isn't a case of test dependency, but what you really want to do is to check if a soft deleted can be permanent deleted (or something like that).
Creating a dependency, in this case, will increase complexity of the test.
It's usually better to mount the test scenario from scratch (data/objects), then execute the logic, and then test if the actual scenario is the expected.
Looked up a few tutorials on facades and laravel 4... tried some... not liked the way they work.
For instance, they don't all provide a way of defining where to store the facade files and service providers... and i tried to step away from that and got my head bumped into a few walls until i decided to do this thread.
So: Let's say i have an app called Laracms (laravel cms).
I'd like to store everything i create - facades, service providers, etc in a folder under app named laracms.
So i'd have /app/laracms/facades, /app/laracms/serviceproviders and so on. I don't want to mix the facades with the database models, i want to keep things as separate as possible.
Let's take now, in my case, the Settings name for the facade (i want to implement a settings class to use in views and admin to set up misc. stuff).
Settings::get(), Settings::set() as methods.
Can anyone explain how to set facades up correctly? I don't know what i'm doing wrong and i need a fresh start.
Thanks,
Chris
Looking for a step by step with simple explanations of how and why.
First you need to go to app/config/app.php and in providers section add:
'Laracms\Providers\SettingsServiceProvider',
In the same file in aliases section you should add:
'Settings' => 'Laracms\Facades\Settings',
In your app/Laracms/Providers you should create file SettingsServiceProvider.php
<?php
namespace Laracms\Providers;
use Illuminate\Support\ServiceProvider;
class SettingsServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind('settings', function()
{
return new \Laracms\Settings();
});
}
}
In your app/Laracms/Facades/ you should create file Settings.php:
<?php
namespace Laracms\Facades;
use Illuminate\Support\Facades\Facade;
class Settings extends Facade {
protected static function getFacadeAccessor() { return 'settings'; }
}
Now in your app/Laracms directory you should create file Settings.php:
<?php
namespace Laracms;
class Settings {
public function get() {echo "get"; }
public function set() {echo "set"; }
}
As you wanted to have your files in custom folder Laracms you need to add this folder to your composer.json (If you used standard app/models folder you wouldn't need to add anything to this file). So now open composer.json file and in section autoload -> classmap you should add app/Laracms so this section of composer.json could look like this:
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php",
"app/Laracms"
]
},
Now you need to run in your console inside your project foler:
composer dump-autoload
to create class map
If everything is fine, you should now be able to use in your applications Settings::get() and Settings:set()
You need to notice that I used folders with uppercases because namespaces by convention starts with upper letters.
There are three components to making a Facade:
The wanna be Facade Class, that class that needs to become a facade.
The Facade required Class, which tells Laravel which registered class it pertains to
A Service Provider, which registers the Facade class in the App container
1. the wanna be Facade Class:
<?php namespace Moubarmij\Services\ModelsServices;
class AuthenticationService extends MoubarmijService implements AuthenticationServiceInterface{
/**
* #param $email
* #param $password
*
* #return mixed
*/
public function login($email, $password)
{
return Sentry::authenticate([
'email' => $email,
'password' => $password,
]);
}
/**
* #return mixed
*/
public function logout()
{
return Sentry::logout();
}
}
2. the required class for the facade to work:
<?php namespace Moubarmij\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Class AuthenticationServiceFacade
* #package Moubarmij\Services\ModelsServices
*/
class AuthenticationServiceFacade extends Facade{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'authentication_service'; }
}
note: authentication_service can be anything you want (its the name of the component registered in the IOC)
3. the service provider
<?php namespace Moubarmij\Providers;
use Illuminate\Support\ServiceProvider;
/**
* A service provider for the Authentication Service
*
* Class AuthenticationServiceSP
* #package Moubarmij\Providers
*/
class AuthenticationServiceSP extends ServiceProvider {
/**
* bind interfaces
*
* #return void
*/
public function register()
{
// Register 'authentication_service' instance container to our AuthenticationService object
$this->app['authentication_service'] = $this->app->share(function($app)
{
return $app->make('Moubarmij\Services\ModelsServices\AuthenticationService');
});
// Shortcut to auto add the Alias in app/config/app.php
$this->app->booting(function()
{
$loader = \Illuminate\Foundation\AliasLoader::getInstance();
$loader->alias('AuthenticationService', 'Moubarmij\Facades\AuthenticationServiceFacade');
});
}
}
I am trying to resolve class via __construct using Laravel's bind() method.
Here what I do:
routes.php (of course I will move it away from here)
// Bindings
App::bind(
'License\Services\ModuleSelector\SelectorInterface',
'License\Services\ModuleSelector\ModuleSelector'
);
SelectorInterface.php - interface that I will expect in __construct method.
<?php namespace License\Services\ModuleSelector;
interface SelectorInterface {
/**
* Simply return query that will select needle module fields
*
* #return mixed
*/
public function make();
}
ModuleSelector.php - this is class that I want to resolve via Laravel's DI (see example below).
<?php namespace License\Services\ModuleSelector;
use License\Services\ModuleSelector\Selector;
class ModuleSelector extends Selector
{
/**
* Get module by it's code
*
* #return mixed
*/
public function find()
{
return $this->make()
->where('code', $module_code)
->first();
}
}
Module.php
<?php namespace License\Services\ModuleType;
use License\Services\ModuleType\TypeInterface;
use License\Services\ModuleSelector\SelectorInterface;
class Module
{
...
function __construct(SelectorInterface $selector)
{
$this->selector = $selector;
}
...
}
And the place when error occurs:
In my repo I have use License\Services\ModuleType\Module as ModuleService;.
Than there is method called find():
/**
* Find module by its code with all data (types, selected type)
* #return mixed
*/
public function find($module_code)
{
$module = new ModuleService;
// Get module id in order to use build in relations in framework
$module = $this->module->find($module_code);
...
}
So, in other words, I have 2 classes and one interface. What I am trying to do is:
1) Create Class1.php / Class2.php / Class2Interface.php.
2) In Class1.php in the __construct I specify __construct(Class2Interface $class2).
3) Instantiate Class2.
What I am doing wrong? Examples found here.
In this line:
$module = new ModuleService;
You are directly invoking the Module class and not passing in an instance of SelectorInterface.
For the IoC to work you bind and make classes using it. Try that line again with :
$module = App::make('License\Services\ModuleSelector\SelectorInterface');
An alernative is to inject it directly into your repos constructor, as long as the repo is created by the IoC container, your concrete will be automatically injected.
Nowhere do you have a class marked to actually "implement SelectorInterface".
I appear to be having issues with my spec tests when it comes to stubs that are calling other methods.
I've been following Laracasts 'hexagonal' approach for my controller to ensure it is only responsible for the HTTP layer.
Controller
<?php
use Apes\Utilities\Connect;
use \OAuth;
class FacebookConnectController extends \BaseController {
/**
* #var $connect
*/
protected $connect;
/**
* Instantiates $connect
*
* #param $connect
*/
function __construct()
{
$this->connect = new Connect($this, OAuth::consumer('Facebook'));
}
/**
* Login user with facebook
*
* #return void
*/
public function initialise() {
// TODO: Actually probably not needed as we'll control
// whether this controller is called via a filter or similar
if(Auth::user()) return Redirect::to('/');
return $this->connect->loginOrCreate(Input::all());
}
/**
* User authenticated, return to main game view
* #return Response
*/
public function facebookConnectSucceeds()
{
return Redirect::to('/');
}
}
So when the route is initialised I construct a new Connect instance and I pass an instance of $this class to my Connect class (to act as a listener) and call the loginOrCreate method.
Apes\Utilities\Connect
<?php
namespace Apes\Utilities;
use Apes\Creators\Account;
use Illuminate\Database\Eloquent\Model;
use \User;
use \Auth;
use \Carbon\Carbon as Carbon;
class Connect
{
/**
* #var $facebookConnect
*/
protected $facebookConnect;
/**
* #var $account
*/
protected $account;
/**
* #var $facebookAuthorizationUri
*/
// protected $facebookAuthorizationUri;
/**
* #var $listener
*/
protected $listener;
public function __construct($listener, $facebookConnect)
{
$this->listener = $listener;
$this->facebookConnect = $facebookConnect;
$this->account = new Account();
}
public function loginOrCreate($input)
{
// Not the focus of this test
if(!isset($input['code'])){
return $this->handleOtherRequests($input);
}
// Trying to stub this method is my main issue
$facebookUserData = $this->getFacebookUserData($input['code']);
$user = User::where('email', '=', $facebookUserData->email)->first();
if(!$user){
// Not the focus of this test
$user = $this->createAccount($facebookUserData);
}
Auth::login($user, true);
// I want to test that this method is called
return $this->listener->facebookConnectSucceeds();
}
public function getFacebookUserData($code)
{
// I can't seem to stub this method because it's making another method call
$token = $this->facebookConnect->requestAccessToken($code);
return (object) json_decode($this->facebookConnect->request( '/me' ), true);
}
// Various other methods not relevant to this question
I've tried to trim this down to focus on the methods under test and my understanding thus far as to what is going wrong.
Connect Spec
<?php
namespace spec\Apes\Utilities;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use \Illuminate\Routing\Controllers\Controller;
use \OAuth;
use \Apes\Creators\Account;
class ConnectSpec extends ObjectBehavior
{
function let(\FacebookConnectController $listener, \OAuth $facebookConnect, \Apes\Creators\Account $account)
{
$this->beConstructedWith($listener, $facebookConnect, $account);
}
function it_should_login_the_user($listener)
{
$input = ['code' => 'afacebooktoken'];
$returnCurrentUser = (object) [
'email' => 'existinguser#domain.tld',
];
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
$listener->facebookConnectSucceeds()->shouldBeCalled();
$this->loginOrCreate($input);
}
So here's the spec that I'm having issues with. First I pretend that I've got a facebook token already. Then, where things are failing, is that I need to fudge that the getFacebookUserData method will return a sample user that exists in my users table.
However when I run the test I get:
Apes/Utilities/Connect
37 ! it should login the user
method `Double\Artdarek\OAuth\Facade\OAuth\P13::requestAccessToken()` not found.
I had hoped that 'willReturn' would just ignore whatever was happening in the getFacebookUserData method as I'm testing that separately, but it seems not.
Any recommendations on what I should be doing?
Do I need to pull all of the OAuth class methods into their own class or something? It seems strange to me that I might need to do that considering OAuth is already its own class. Is there some way to stub the method in getFacebookUserData?
Update 1
So I tried stubbing the method that's being called inside getFacebookUserData and my updated spec looks like this:
function it_should_login_the_user($listener, $facebookConnect)
{
$returnCurrentUser = (object) [
'email' => 'existinguser#domain.tld',
];
$input = ['code' => 'afacebooktoken'];
// Try stubbing any methods that are called in getFacebookUserData
$facebookConnect->requestAccessToken($input)->willReturn('alongstring');
$facebookConnect->request($input)->willReturn($returnCurrentUser);
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
$listener->facebookConnectSucceeds()->shouldBeCalled();
$this->loginOrCreate($input);
}
The spec still fails but the error has changed:
Apes/Utilities/Connect
37 ! it should login the user
method `Double\Artdarek\OAuth\Facade\OAuth\P13::requestAccessToken()` is not defined.
Interestingly if I place these new stubs after the $this->getFacebookUserData stub then the error is 'not found' instead of 'not defined'. Clearly I don't fully understand the inner workings at hand :D
Not everything, called methods in your dependencies have to be mocked, because they will in fact be called while testing your classes:
...
$facebookConnect->requestAccessToken($input)->willReturn(<whatever it should return>);
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
...
If you don't mock them, phpspec will raise a not found.
I'm not familiar with the classes involved but that error implies there is not method Oauth:: requestAccessToken().
Prophecy will not let you stub non-existent methods.
I have an DB table with a UNIQUE column that should contain a unique 8 character alphanumeric string.
I've (finally) making the move from my own MVC framework to symfony. Up until now I would have had a private method in the model that is called on CREATE. A loop in the method would generate a random hash, and perform a READ on the table to see if it is unique: if so, the hash would be returned and injected into the CREATE request.
The problem as I see it is that in symfony I have no access to the repository from within the entity class, so I can't use a lifecycle callback. I understand the reasoning behind this. On the other hand, the hash generation has nothing to do with the controller – for me it is internal logic that belongs in the model. If I later change the data structure, I need to edit the controller.
My question is: architecture-wise, where should I put the hash generation method?
You can use a listener. You were right that the lifecycle callbacks are not the correct solution since you need access to the repository. But you can define a Listener that listens to the same event as the lifecycle callback, but is an service and therefore can have the repository as dependency.
Answering my own question:
I created a custom repository, which has access to the doctrine entity manager.
The repository has a createNewHash method:
class HashRepository extends EntityRepository
{
public function createNewHash()
{
$hash = new Hash();
$hash->setHash($this->_getUniqueHash());
$em = $this->getEntityManager();
$em->persist($hash);
$em->flush();
return $hash;
}
private function _getUniqueHash()
{
$hash = null;
$hashexists = true;
while ($hashexists) {
$hash = $this->_generateRandomAlphaNumericString();
if (!$hashobject = $this->findOneByHash($hash)) {
$hashexists = false;
}
}
return $hash;
}
private function _generateRandomAlphaNumericString( $length=8 )
{
$bits = $length / 2;
return bin2hex(openssl_random_pseudo_bytes($bits));
}
}
The createNewHash() method can then be called from the Controller, and the Controller does not have to concern itself with hash creation.
EDIT: Listeners are another way of doing it.
In your entity constructor, i can add this:
<?php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* MyEntity
*
* #ORM\Table(name="my_entity")
*/
class MyEntity
{
/**
* #ORM\Column(type="string", length=8, unique=true, nullable=false)
* #var string
*/
private $uniqId;
public function __construct()
{
$this->uniqId = hash('crc32b', uniqid());
}
// ...
}
Hope this helps