How to use multiple dataproviders in a single codeception(a php based test automation framework) test? - codeception

I use codeception which is a php based test automation framework.
I need to use multiple dataproviders in a single test class.
I know how to use a single dataprovider per test; example of my code below:
use Codeception\Example;
/**
* #dataProvider charProvider
*/
public function sampleTest(Example $data) {
//iterating through the array of data points contained in the data provider:
$data_point_char = iterator_to_array($data->getIterator());
print_r($data_point_char['data']);
}
/**
* #return array
*/
protected function charProvider() {
return[
['data' => 'a'],
['data' => 'b']
];
}
Now my requirement is to use another data provider: numProvider() within the same test:
protected function numProvider() {
return[
['data' => 1],
['data' => 2]
];
}
I am not sure, how the iterator_to_array() will reference this second data provider and iterate through it as in the code I used, this iterator_to_array() method is using a pretty generic parameter:
iterator_to_array($data->getIterator())
Not quite getting how individual data provider can be referenced in this manner?
And if individual dataproviders can be referenced independently within a same test, then following that multiple dataproviders can be used within the same test.
Kindly suggest how to achieve this?
Thanks in advance

Codeception 5 allows to specify multiple data providers for test method. Test method is executed repeatedly with each data item from each data provider, in your example sampleTest will be executed 4 times.
It is completely unnecessary to use iterator_to_array($data->getIterator()); for converting instance of Example class to array. Example implements ArrayAccess interface, so data from data provider can be accessed as array element - $example['data'].
/**
* #dataProvider charProvider
* #dataProvider numProvider
*/
public function sampleTest(Example $example) {
var_dump($example['data']);
}

Related

In Specflow can I perform setup once then run a bunch of independent verifications as separate tests?

I'm writing automated tests for a set of APIs. I have 1 API to run a search and another to get the results. I want to run the search once then have several tests Get the results and run verifications.
RunOnce:
Given I have access to the User 'testUser'
When I call POST /api/Search with
"""
{
"query": "legal",
"snippetLength": 15,
"resultSize": 4,
...
}
"""
Scenario: SearchAPI - Verify Result Size
When I call GET /api/Search
Then The result size is X
Scenario: SearchAPI - Verify Filters
When I call GET /api/Search
Then The filters are X
Scenario: SearchAPI - Verify Sorting
When I call GET /api/Search
Then The sorting is X
Scenario: SearchAPI - Verify snippet
When I call GET /api/Search
Then The snippet length is X
Is this a really bad pattern? I don't want to have to run the search for each Scenario due to timing. But at the same time I want the tests separate so I don't have all the Assertions in a single scenario for efficient result analysis.
I think the only way to do this would be adding a tag to the Feature and implementing this in the TestHooks file. But then I lose a lot of the visibility if there are failures in the POST call.
There is no Gherkin language feature that allows you to do this. Instead, consider using the FeatureContext to store the search results, since the FeatureContext object is shared by all scenarios in a feature. You would still use a scenario background for your Given step that submits the search. The step definition would check to see if the feature context already has the search results, and if so, the Given step would simply do nothing. If the search results do not exist in the feature context, then execute the code to POST the search.
Any When or Then steps operating on the search results should pull the results from the feature context.
Feature file:
Background:
Given I have access to the User 'testUser'
When I call POST /api/Search with
"""
{
"query": "legal",
"snippetLength": 15,
"resultSize": 4,
...
}
"""
Scenario: SearchAPI - Verify Result Size
When I call GET /api/Search
Then The result size is X
Scenario: SearchAPI - Verify Filters
When I call GET /api/Search
Then The filters are X
Scenario: SearchAPI - Verify Sorting
When I call GET /api/Search
Then The sorting is X
Scenario: SearchAPI - Verify snippet
When I call GET /api/Search
Then The snippet length is X
Next, use the FeatureContext to store and retrieve the searchId, and results:
[Binding]
public class SearchSteps
{
private readonly FeatureContext feature;
/// <summary>
/// Gets or sets the API search Id
/// </summary>
private string SearchId
{
get => (string)feature["foo"];
set => feature["foo"] = value;
}
/// <summary>
/// Gets or sets the API search results
/// </summary>
private IEnumerable<X> Results
{
get => (IEnumerable<X>)feature["searchResults"];
set => feature["searchResults"] = value;
}
public SearchSteps(FeatureContext feature)
{
this.feature = feature;
}
[When(#"I call POST /api/Search with")]
public void WhenICallPOST_api_searchWith(string json)
{
if (SearchId != null)
return;
var request = // parse json variable into object
SearchId = api.PostSearch(request);
}
[When(#"I call GET /api/Search")]
public void WhenICallGET_api_Search()
{
Results = api.GetSearch(SearchId);
}
[Then(#"The result size is (\d+)")]
public void ThenResultSizeIs(int expectedResultSize)
{
Assert.AreEqual(Results.Count(), expectedResultSize);
}
// Other 'Then' steps make assertions on Results property
}
You can magically get access to the FeatureContext, because SpecFlow already registers that object with the dependency injection framework. Simply add a FeatureContext parameter to the constructor for your step definition to get access to this object.
I find it nice to create private properties that get/set values on the FeatureContext object, so you only cast objects in one spot. Plus it gives things a meaningful name, and makes dealing with the FeatureContext easier in your step definitions.
You would replace IEnumerable<X> Results with the name of the type for the response to GET /api/Search/:searchId.

CakePHP3: Mock methods in integration tests?

I'm new to unit / integration testing and I want to do an integration test of my controller which looks simplified like this:
// ItemsController.php
public function edit() {
// some edited item
$itemEntity
// some keywords
$keywordEntities = [keyword1, keyword2, ...]
// save item entity
if (!$this->Items->save($itemEntity)) {
// do some error handling
}
// add/replace item's keywords
if (!$this->Items->Keywords->replaceLinks($itemEntity, $keywordEntities)) {
// do some error handling
}
}
I have the models Items and Keywords where Items belongsToMany Keywords. I want to test the error handling parts of the controller. So I have to mock the save() and replaceLinks() methods that they will return false.
My integration test looks like this:
// ItemsControllerTest.php
public function testEdit() {
// mock save method
$model = $this->getMockForModel('Items', ['save']);
$model->expects($this->any())->method('save')->will($this->returnValue(false));
// call the edit method of the controller and do some assertions...
}
This is working fine for the save() method. But it is not working for the replaceLinks() method. Obviously because it is not part of the model.
I've also tried something like this:
$method = $this->getMockBuilder(BelongsToMany::class)
->setConstructorArgs([
'Keywords', [
'foreignKey' => 'item_id',
'targetForeignKey' => 'keyword_id',
'joinTable' => 'items_keywords'
]
])
->setMethods(['replaceLinks'])
->getMock();
$method->expects($this->any())->method('replaceLinks')->will($this->returnValue(false));
But this is also not working. Any hints for mocking the replaceLinks() method?
When doing controller tests, I usually try to mock as less as possible, personally if I want to test error handling in controllers, I try to trigger actual errors, for example by providing data that fails application/validation rules. If that is a viable option, then you might want to give it a try.
That being said, mocking the association's method should work the way as shown in your example, but you'd also need to replace the actual association object with your mock, because unlike models, associations do not have a global registry in which the mocks could be placed (that's what getMockForModel() will do for you) so that your application code would use them without further intervention.
Something like this should do it:
$KeywordsAssociationMock = $this
->getMockBuilder(BelongsToMany::class) /* ... */;
$associations = $this
->getTableLocator()
->get('Items')
->associations();
$associations->add('Keywords', $KeywordsAssociationMock);
This would modify the Items table object in the table registry, and replace (the association collection's add() acts more like a setter, ie it overwrites) its actual Keywords association with the mocked one. If you'd use that together with mocking Items, then you must ensure that the Items mock is created in beforehand, as otherwise the table retrieved in the above example would not be the mocked one!

Set the default __toString() format per Carbon instance?

I retrieve dates from a database and have the option to pre-process them (via the Laravel framework (v5.2)). The dates or times can come in any particular format but for this example let's say Y-m-d.
I want to be able to access the date as a Carbon instance in the view — this would give me the flexibility to format the date however I please or do nothing with it and print as-is (with the default toString being the same as its original string format).
The issue is maintaining the default expected toString format at the top-level Carbon toString format.
According to the docs, you can use the ::setToStringFormat() method to change the default format of toString. It is possible to set it with the static method Carbon::setToStringFormat() but it also works as an instance method e.g. ($date = Carbon::now())->setToStringFormat('Y-m-d') - albeit this seems to behave identically to the static method.
So, is it possible to set the individual __toString() format for a date instance?
It would allow me to do the following:
public function getDateAttribute($value)
{
$date = Carbon::createFromFormat('Y-m-d', $value);
// $date->setToStringFormat('Y-m-d');
return $date; // prints in 'Y-m-d' format
}
In a view, I would then chain methods on the date, or print it as-is.
I had the same sort of issue and worked my way around it
First create a global serialization method for carbon dates (e.g. in the boot of the Laravel AppServiceProvider)
<?php
namespace App\Providers;
use Illuminate\Support\Carbon;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot() {
// ...
Carbon::serializeUsing(function (Carbon $carbon):string {
$format = $carbon->getSettings()['toStringFormat'] ?? 'Y-m-d\TH:i:s';
return $carbon->format($format);
});
// ...
}
// ...
}
Then set the 'toStringFormat' setting to the format you need
$someCarbonDate->settings([ 'toStringFormat' => 'Y-m-d' ]);
Carbon::serializeUsing will now check if the carbon being serialized has a toStringFormat and use that or it will fall back to some other format you define.
You could probably also create your own Carbon class and extend Carbon\Carbon or Illuminate\Support\Carbon (used by Laravel) and override the __toString method, but that creates some new challenges when used in combination with the casting Laravel does internally.
If you just want to set the default format for when rendering Blade templates you can use the Blade::stringable method.
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
// ...
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot() {
// ...
Blade::stringable(function(\Illuminate\Support\Carbon $dateTime) {
return $dateTime->format('d/m/Y H:i:s');
});
// ...
}
// ...
}
Any Illuminate\Support\Carbon instances should automatically be rendered with this format unless you choose otherwise.
For example...
{{ $user->created_at }} would now render as 23/07/2021 10:16:24 by default
{{ $user->created_at->toDateString() }} still works and would render as 23/07/2021
NOTE: You might need to run php artisan view:clear to clear any compiled views before this takes effect 🙂

Automatic object cache proxy with PHP

Here is a question on the Caching Proxy design pattern.
Is it possible to create with PHP a dynamic Proxy Caching implementation for automatically adding cache behaviour to any object?
Here is an example
class User
{
public function load($login)
{
// Load user from db
}
public function getBillingRecords()
{
// a very heavy request
}
public function computeStatistics()
{
// a very heavy computing
}
}
class Report
{
protected $_user = null;
public function __construct(User $user)
{
$this->_user = $user;
}
public function generate()
{
$billing = $this->_user->getBillingRecords();
$stats = $this->_user->computeStatistics();
/*
...
Some rendering, and additionnal processing code
...
*/
}
}
you will notice that report will use some heavy loaded methods from User.
Now I want to add a cache system.
Instead of designing a classic caching system, I just wonder if it is possible to implement a caching system in a proxy design pattern with this kind of usage:
<?php
$cache = new Cache(new Memcache(...));
// This line will create an object User (or from a child class of User ex: UserProxy)
// each call to a method specified in 3rd argument will use the configured cache system in 2
$user = ProxyCache::create("User", $cache, array('getBillingRecords', 'computeStatistics'));
$user->load('johndoe');
// user is an instance of User (or a child class) so the contract is respected
$report = new report($user)
$report->generate(); // long execution time
$report->generate(); // quick execution time (using cache)
$report->generate(); // quick execution time (using cache)
each call to a proxyfied method will run something like:
<?php
$key = $this->_getCacheKey();
if ($this->_cache->exists($key) == false)
{
$records = $this->_originalObject->getBillingRecords();
$this->_cache->save($key, $records);
}
return $this->_cache->get($key);
Do you think it is something we could do with PHP? do you know if it is a standard pattern? How would you implement it?
It would require to
implement dynamically a new child class of the original object
replace the specified original methods with the cached one
instanciate a new kind of this object
I think PHPUnit does something like this with the Mock system...
You can use the decorator pattern with delegation and create a cache decorator that accepts any object then delegates all calls after it runs it through the cache.
Does that make sense?

Yii Framework: CModel replicating CActiveRecord functionality with WebServices

Has anyone tried or found an example of a class derived from CModel that replicates CActiveRecord functionality with WebServices instead of database connection???
If done with RESTFULL WebServices it would be great. If data is transmitted JSON encoded, wonderful!!...
I'd appretiate your help. Thanks.
I spend a lot of time looking for that as well, I came across this Yii extension on Github:
https://github.com/Haensel/ActiveResource
It allows you to have exactly what you are looking for, the readme isn't updated with the changes reflected in changes.md, so I recommend you read through this document as well.
EActiveResource for Yii
...is an extension for the Yii PHP framework allowing the user to create models that use RESTful services as persistent storage.
The implementation is inspired by Yii's CActiveRecord class and the Ruby on Rails implementation of ActiveResource (http://api.rubyonrails.org/classes/ActiveResource/Base.html).
HINT:
CAUTION: THIS IS STILL AN ALPHA RELEASE!
This project started as a draft and is still under development, so as long is there is no 1.0 release you may experience changes that could break your code. Look at the CHANGES.md file for further information
As there are thousands of different REST services out there that use a thousand different approaches it can be tricky to debug errors. Because of that I added extensive
tracing to all major functions, so you should always be able to see every request, which method it used and how the service responded. Just enable the tracing functionality of Yii
and look for the category "ext.EActiveResource"
INSTALL:
Add the extension to Yii by placing it in your application's extension folder (for example '/protected/extensions')
Edit your applications main.php config file and add 'application.extensions.EActiveResource.*' to your import definitions
Add the configuration for your resources to the main config
'activeresource'=>array(
'class'=>'EActiveResourceConnection',
'site'=>'http://api.aRESTservice.com',
'contentType'=>'application/json',
'acceptType'=>'application/json',
)),
'queryCacheId'=>'SomeCacheComponent')
4.) Now create a class extending EActiveResource like this (don't forget the model() function!):
QUICK OVERVIEW:
class Person extends EActiveResource
{
/* The id that uniquely identifies a person. This attribute is not defined as a property
* because we don't want to send it back to the service like a name, surname or gender etc.
*/
public $id;
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function rest()
{
return CMap::mergeArray(
parent::rest(),
array(
'resource'=>'people',
)
);
}
/* Let's define some properties and their datatypes
public function properties()
{
return array(
'name'=>array('type'=>'string'),
'surname'=>array('type'=>'string'),
'gender'=>array('type'=>'string'),
'age'=>array('type'=>'integer'),
'married'=>array('type'=>'boolean'),
'salary'=>array('type'=>'double'),
);
}
/* Define rules as usual */
public function rules()
{
return array(
array('name,surname,gender,age,married,salary','safe'),
array('age','numerical','integerOnly'=>true),
array('married','boolean'),
array('salary','numerical')
);
}
/* Add some custom labels for forms etc. */
public function attributeLabels()
{
return array(
'name'=>'First name',
'surname'=>'Last name',
'salary'=>'Your monthly salary',
);
}
}
Usage:
/* sends GET to http://api.example.com/person/1 and populates a single Person model*/
$person=Person::model()->findById(1);
/* sends GET to http://api.example.com/person and populates Person models with the response */
$persons=Person::model()->findAll();
/* create a resource
$person=new Person;
$person->name='A name';
$person->age=21;
$person->save(); //New resource, send POST request. Returns false if the model doesn't validate
/* Updating a resource (sending a PUT request)
$person=Person::model()->findById(1);
$person->name='Another name';
$person->save(); //Not at new resource, update it. Returns false if the model doesn't validate
//or short version
Person::model()->updateById(1,array('name'=>'Another name'));
/* DELETE a resource
$person=Person::model()->findById(1);
$person->destroy(); //DELETE to http://api.example.com/person/1
//or short version
Person::model()->deleteById(1);
Hope this helps you