I'm learning this amazing Test Framework (Codeception) I already wrote a bunch of test as exercise and all are passed correctly.
Obviously I came a cross a same questions regarding the architecture and the reusability of the tests and the best practice on how use the different Class to structure the tests.
Cest and Cept:
How I read on the documentation there are typology of tests cept and cest.
I experimented both writing tests but still the quantity that I done did not understand me when is the best use of each of them. The only thing I read on the documentation is the reason that if your test is too long create a Cest Class is the best approach.
Global helpers
Going to create a functional test for my login form I structured it like so:
$I = new TestGuy($scenario);
$I->am('A member');
$I->wantTo('Login in the application');
$I->amOnPage('/');
$I->signIn(); // This custom method belongs to a helper
$I->seeInCurrentUrl('/home');
Review the SignIn method
/**
* Class TestHelper
*
* #package Codeception\Module
*/
class TestHelper extends \Codeception\Module
{
/**
* Sign In a user
*/
public function signIn()
{
$email = 'test#test.com';
$password = 'Hello123';
$username = 'user3';
// create a dummy account
$this->haveAnAccount(compact('email','password','username'));
$I = $this->getModule('Laravel4');
$I->fillField('.navbar-collapse input[name="email"]',$email);
$I->fillField('.navbar-collapse input[name="password"]',$password);
$I->click('LOG IN');
}
}
This test pass correctly.
Now If I want to use the signIn() Method in my functional tests is pretty straight forward just use it.
The issue is came when I had to create an Acceptance test with Selenium and it require the exactly the same process to get the user logged in and I need to reuse the exactly the same method that in this circumstance I cannot use for acceptance.
So witch is the best practice to share and make global this helpers?
This helper is already 'global'.
Everything you need is to add this helper to needed suite config - acceptance.suite.yml:
class_name: AcceptanceTester
modules:
enabled: [AcceptanceHelper, YourHelper]
and then do not forget to run
php codeception.phar build
Related
I'm looking into using DataFactory's in Codeception for seeding of data, and for use in our acceptance tests. In the documentation there's mention of 2 approaches, one using the helper file and one using factories files.
We load both options using this snippet from our acceptance.suite.yml
class_name: AcceptanceTester
modules:
enabled:
- Db
- WebDriver
- \Helper\Acceptance
- Doctrine2:
connection_callback: getEntityManager
- DataFactory:
factories: tests/_support/factories
depends: Doctrine2
- \Helper\Factory
Both of the options seem to load correctly. As per the documentation I can then define factories like this, which will allow interaction with Doctrine.
// tests/_support/Helper/Factory.php
class Factory extends Module
{
/**
* #param array $settings
* #throws \League\FactoryMuffin\Exceptions\DefinitionAlreadyDefinedException
* #throws \Codeception\Exception\ModuleException
*/
public function _beforeSuite($settings = [])
{
/** #var Module\DataFactory $factory */
$factory = $this->getModule('DataFactory');
/** #var EntityManager $em */
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(User::class,[
// generate random user name
'name' => Faker::name(),
]);
parent::_beforeSuite($settings);
}
}
As per the other option, I can also create factories by loading all files from within tests/_support/factories, such as below:
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
/** #var \League\FactoryMuffin\FactoryMuffin $fm */
$user = $fm->create(User::class);
dd($user);
However, the seed.php version cannot seem to share the Factory, and errors with:
The model definition 'User' is undefined.
I wondered if maybe this could be solved by moving the Factory.php logic into the initialize() method but this seems to be called before FactoryMuffin has been initiliazed.
The documentation for this with codeception seems a bit sparse, and the FactoryMuffin docs, while better, don't cover Codeception integration. Just trying to work out if i'm missing something, or I just need to repeat the code in each place if I want to use both files/methods.
This is an old question and technology moves fast so the documentation has likely changed since this was originally asked but I'll make an attempt in case anyone else stumbles across it like I did.
You're using the DataFactory module which is great as it comes with the integration for Codeception out of the box. The two methods you've described are actually ways of integrating DataFactory with your data. By creating factory files, you've given DataFactory a means of generating data. But what if you have some data already in the database that you'd like to use in your tests as well? That's where you would use the Helper class. According to the DataFactory Module docs:
In cases you want to use data from database inside your factory definitions you can define them in Helper. For instance, if you use Doctrine, this allows you to access EntityManager inside a definition.
As for your issue of seed.php not finding the User model, you need to specify it according to the definition given in your factory. For example, if your factory file looks similar to this
<?php
use League\FactoryMuffin\Faker\Facade as Faker;
$fm->define('app\models\User')->setDefinitions([
'name' => Faker::name(),
... // the rest of your properties here
]);
Then seed.php would look like
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
$user = $fm->create('app\models\User');
Once you have the DataFactory module installed and configured, you can simply call it within the appropriate testing suite via have, haveMultiple, or make. See the Codeception Docs
I was writing a custom class to handle some unique operations in my project, which is developed in Laravel 5.2. In that, I've called some existing model functions also to fetch some values from the database. If we run the code, it'll work fine. But when I wrote different test cases and tested those classes alone, a database connection error has occurred, as the connection was not established.
PHP Fatal error: Call to a member function connection() on null in /var/www/html/****/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php on line 3314
I could write a separate test code to create and delete resources in the database. But that is not what I'm searching for. I wanted to use custom functions written in the model class like shown below.
<?php
namespace ****\Paymentmethod;
use App\Models\Customer;
/**
* ****** Package - Additional helpers
* #package *******
* #author ********
*
* Card is intended to handle the operations related to credit cards
*
*/
class Card
{
private $var;
public function __construct()
{
//constructor
}
public static function getCard($user_id)
{
return Customer::getCustomerInfo($user_id);
}
}
This Card class is used in another class and that is what I'm testing.
Customer is a model class and getCustomerInfo is a custom method in it.
You're app instance is probably not instantiated.
Have a look at the TestCase.php that Laravel provides in the tests/ folder, it creates an instance of the app. Unless your test is a pure 'unit' test which does not rely on the framework at all, you need to make sure your test extends the TestCase class so that it too will instantiate an application instance.
The reason it works when you run outside of the tests is when you enter the application via a route or a console command, the application is bootstrapped for you.
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');
}
}
What I am currently trying to do is to test the constructor of a class in an unit test.
I am not sure whether the instance of this object is a "god object", I would say it's not, since it only aggregates several other components.
Either way, I am open to a better design.
So the rough class diagram looks like this
World is the suspected god class. Its dependencies, ServiceProvider, CommandRegistry, EventHub and Environment are injected via its constructor.
In its constructor, World does the following things:
store its dependencies in private fields
register a hook ($this, 'onCommandIssued') with the eventHub so that the world receives notifications about all commands being executed not through the world instance itself (world having also a method executeCommand)
tell the environment to adopt the world: $this->environment->adoptWorld($this). The environment's role is to adapt the world to some of the realities of the running environment, for instance a web environment has some specific services which are not available in a console application environment (e.g. the "session" service)
notify via the event hub that the construction of the world has finished: $this->eventHub->notify(new WorldConstructedEvent($this));
Maybe this looks like a heavy constructor, but it simply is what is defined as "constructing the world".
The World is basically the gateway to send commands (as Data Transfer Objects, via World::executeCommand()) to which different services can register hooks.
Now to the problems/questions:
I am trying to unit-test this constructor, but I have to add a bunch of #uses annotations, which makes it feel like anything else but an unit test. What is it then, a functional test? Unit-testing World is awkward, testing anything else is really trivial and I don't see this problem emerging in any other test, which makes me ask myself why that is and how to improve the design.
Is World a god object? All it does is to aggregate other components and forward calls to them.
How to properly unit-test the methods of World? If I use lots of stubs and I dependency-inject them, is it still unit-testing?
This is for a domain-driven designed (complex) application and I am open to suggestions which would make the design better (testable and decoupled).
Please let me know in the comments if you need any more details.
As I don't know where this discussion will lead to, I may refine my questions.
I finally got to unit-test the class by setting up proper expectations and mock the dependencies:
<?php
namespace Common\World;
use TestFramework\TestCase;
class WorldTest extends TestCase
{
/**
* #test
* #covers \Common\World\World::__construct
* #uses \Common\World\World::setEventHub
* #uses \Common\World\Event\Adopted
*/
public function construction()
{
/** #var \Common\World\Environment $environmentStub |\PHPUnit_Framework_MockObject_MockObject */
$environmentStub = $this->getMockBuilder('Common\World\Environment')->getMock();
/** #var \Common\World\EventHub|\PHPUnit_Framework_MockObject_MockObject $eventHubStub */
$eventHubStub = $this->getMock('Common\World\EventHub');
$environmentStub->expects($this->once())
->method('adoptWorld')
->will($this->returnCallback(function (World $world) use ($eventHubStub) {
$world->setEventHub($eventHubStub);
return true;
}));
$eventHubStub->expects($this->once())
->method('trigger')
->with($this->isInstanceOf('Common\World\Event\Adopted'));
$this->assertInstanceOf('Common\World\World', new World($environmentStub));
}
/**
* #test
* #covers \Common\World\World::__construct
* #expectedException \RuntimeException
* #expectedExceptionMessage the environment has rejected this world for incompatibility reasons
*/
public function construction_rejected()
{
/** #var \Common\World\Environment $environmentStub */
$environmentStub = $this->getMockBuilder('Common\World\Environment')->getMock();
$environmentStub->expects($this->once())
->method('adoptWorld')
->will($this->returnValue(false));
new World($environmentStub);
}
}
I am using PHPUnit and Selenium to test my web application.
At the moment I have 2 test classes - UserTest and PermissionsTest.
In UserTest I have methods which test that the program can successfully create a new user.
In PermissionsTest I turn certain permissions on and off and test the outcome.
For instance, I might turn the 'Create User' permission off and then test that the 'Create User' button is disabled. However, if I turn the 'Create User' permission back on, I want to test that it is possible to create a user.
All of the logic for being able to create a user is already in the UserTest class - so is there any way of running tests from the UserTest class from the PermissionsTest class?
At the moment I am trying the following code:
public function testUserPermission(){
$userTest = new UserTest();
if($this->hasPermission = true){
$userTest->testCanCreateUser();
}
}
However when I run this test, I get the error "There is currently no active session to execute the 'execute' command. You're probably trying to set some option in setup() with an incorrect setter name..."
Thanks!
It sounds to me like you're missing separation of your test implementation with its logic - I'm not talking about PHP issue but general test model.It will allow you to reuse your test components in various test cases.
You can take a look on some
material about Page Objects in PHP here or general selenium wiki.
The solution was as follows:
//instantiate and set up other test class
$userTest = new UserTest();
$userTest->setUpSessionStrategy($this::$browsers[0]);
$userTest->prepareSession();
//carry out a test from this class
$userTest->testCanCreateUser();
This works nicely. I can't see why using functionality from another test class is a bad idea in this case, because if I didn't do that I'd have to just rewrite that functionality into my new class, which seems less 'pure'...
For Selenium 1 (RC),
I made the following modifications instead (as well as applying the Page Object design pattern):
Specific Test class
//instantiate and set up other test class
$userTest = new UserTest($this->getSessionId());
//carry out a test from this class
$userTest->createUser();
//asserts as normal
$userTest->assertTextPresent();
...
Base Page Object class
class PageObject extends PHPUnit_Extensions_SeleniumTestCase {
public function __construct($session_id) {
parent::__construct();
$this->setSessionId($session_id);
$this->setBrowserUrl(BASE_URL);
}
}
Specific Page Object class
class UserTest extends PageObject {
public function createUser() {
// Page action
}
}