I want to test whether my sorting works properly with Laravel TestCase. I have simple test page:
<div id="values">
#foreach(['Value1', 'Value2'] as $value)
<div class="test-class">{{$value}}</div>
#endforeach
</div>
And now i want to test if the first .test-class element contains 'Value1'. I had tried different approaches, and had no success. Here are first one:
public function testSorting()
{
$this->visit(route('dummy'));
$this->see('Value1');
$this->seeInElement('.test-class:first-of-type', 'Value2');
}
This one resulted in exception:
Symfony\Component\CssSelector\Exception\ExpressionErrorException: "*:first-of-type" is not implemented.
Then I tried
$this->seeInElement('#values:first-child', 'Value2');
and my test when green. But it should be red, because first child is 'Value1'. It totally ignored ':first-child' part.
Then I tried
$this->seeInElement('#values:nth-child(1)', 'Value2');
and got green too. So this approach doesn't work either.
Have I missed something? Or there is no way to test order of elements on page with Laravel tests?
I'm late to the party here, but I've been using the below to check the ordering of items on the page. Seems to work out so well so far.
Implement the below method on your TestCase base class.
/**
* Given a CSS selector string, check if the elements matching the query
* contain the values provided, in the order they are provided.
*
* #param string $selector
* #param array $contents
* #return $this
*/
public function seeInOrder($selector, Array $contents)
{
$matches = $this->crawler->filter($selector);
try {
foreach ($matches as $index => $domElement) {
$needle = $contents[$index];
$this->assertContains($needle, trim($domElement->textContent));
}
} catch (PHPUnit_Framework_ExpectationFailedException $e) {
$this->fail('Failed asserting that the element at index ' . $index . ' contains the string "' . $contents[$index] . '"');
}
return $this;
}
Usage:
/**
* Test that the /books route returns a list of books in explicit order.
*
* #test
* #return void
*/
public function books_index_page_lists_books_in_explicit_order()
{
$book_1 = factory(App\Book::class)->create([
'order' => 0
]);
$book_2 = factory(App\Book::class)->create([
'order' => 1
]);
$this->visit('/books')
->see($book_1->title)
->see($book_2->title)
->seeInOrder('.books .book', [
$book_1->title,
$book_2->title
]);
}
Not a solution for your specific question, but a workaround for your problem:
You could enumerate your test-class divs with the $loop->iteration helper variable, which contains the number of the current loop iteration.
<div id="values">
#foreach(['Value1', 'Value2'] as $value)
<div class="test-class" id="value_{{$loop->iteration}}">{{$value}}</div>
#endforeach
</div>
Now your div classes have an enumerated id beginning with 1 and you can use the selector #value_1 in your test to select the first div:
$this->seeInElement('#value_1', 'Value2');
I stumbled onto this question for my HTTP test, if you're using that no need to build your own function like in the answers below. Laravel ships with a bunch of assertions, including assertSeeInOrder:
https://laravel.com/docs/7.x/http-tests#assert-see-in-order
So, i have solved it. The key was to look into \Symfony\Component\DomCrawler\Crawler class. It has a lot of useful methods like first(), last() or eq($number). I have created this method in my TestCase class:
public function seeInFirstElement($selector, $text)
{
$this->assertContains($text, trim($this->crawler->filter($selector)->first()->text()));
return $this;
}
You can get rid of trim() function but with it the output looks better in case of failed test.
So, in my tests I use such constructions to test ordering:
$this->seeInFirstElement('.test-class', 'Value1');
Related
I have this controller for a RESTful API I am building in Laravel Lumen which takes a relatively big amount of parameters and parses them into where queries, and data is fetched depending on if they were provided. For example,
GET /nodes?region=California
GET /nodes?ip=127.0.0.1
I am currently taking them in the constructor, building an array of the parameters (since I couldn't figure out how to get the raw get array in Lumen and it would be inconvenient because I already have other parameters there), and filtering out the null values (I am setting values to null if they are not in the query).
Now, when it comes to filtering the values each in the array, I am doing it by a foreach array. This is the cleanest way I could figure out to do it, without too much code (I don't want to make my controllers too fat.).
Is there any other way to do this cleanly, maybe with separation of functions/classes?
Here is my constructor code:
/**
* Get some values before using functions.
*
* #param Request $request Instance of request.
*/
public function __construct(Request $request)
{
$this->offset = (int) $request->input('offset', 0);
// TODO: I'm not sure how to implement this, code in question
$this->filters = [
'region' => $request->input('region', null),
'name' => $request->input('name', null),
'ip' => $request->input('ip', null)
];
$this->filters = array_filter($this->filters, function ($v) {
return !is_null($v);
});
// Set a sane SQL limit.
$this->limit = 5;
$this->request = $request;
}
And the controller code:
/**
* List all nodes.
*
* #return [string] [JSON containing list of nodes, if sorted.]
*/
public function all()
{
try {
// use filters provided
$data = Nodes::limit($this->limit)->offset($this->offset);
foreach ($this->filters as $filter => $value) {
$data->where($filter, $value);
}
$data = $data->get();
$response = $this->respond($data);
} catch (\Exception $e) {
$response = $this->respondServerError('Could not retrieve data from database.');
}
return $response;
}
So any time I have to do filtering of a resource-list in an API, here's how I do it.
First off though, before I begin, a quick tip concerning getting the Request object when you're in your controller method: If you add Request $request as a parameter for your all() function, you will have access to the $request variable there, same as your constructor. So the complete signature would be public function all(Request $request). Controller methods have the same magic dependency injection that other class constructors get in Laravel/Lumen. Alternatively, in your function you can always ask the app() function to give you an object of a specific class. Because the Request object is bound in the Container to just 'request', you can ask for the full class name, or just 'request': $request = app('request');
So once I have my request object, inside my controller method I like to go through each filter either as a group, or one-by-one, depending on how complex each filter is. Sometimes filters are complex, like a list of comma-separated IDs that need to be exploded into an array. If it's just simple string filters though, I tend to throw the list into an array and run through that.
Here's an example function to illustrate some ideas:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
/**
* Continue with the rest of response formatting below here
*/
}
You'll notice I used the paginate function to limit my results. When building an API endpoint that lists resources, you're going to want to put in your headers (my preference) or the response body information on how to get the first, previous, next, and last page of results. The Pagination feature in Laravel makes that easy, as it can construct most of the links using the links() method.
Unfortunately, you need to tell it what filter parameters were passed in the request so it can make sure it adds those to the links it generates. Otherwise you'll get links back without your filters, which doesn't do the client very much good for paging.
So here's a more complete example of recording filter parameters so they can be appended onto pagination links:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//List of filters we found to append to links later
$appends = [];
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$appends[$field] = $request->input($field);
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$appends['email'] = $request->input('email');
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$appends['id'] = $request->input('id');
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
//Make sure we append our filter parameters onto the pagination object
$users->appends($appends);
//Now calling $users->links() will return the correct links with the right filter info
/**
* Continue with the rest of response formatting below here
*/
}
Pagination documentation can be found here: https://laravel.com/docs/5.2/pagination
For an example of how pagination linking can be awesomely done, check out Github's API documentation: https://developer.github.com/v3/#pagination
In the end it's not too far off from what you were doing, conceptually. The advantage here is that you move the code into the method that needs it, instead of having it run in your constructor every single time the controller is initialized, even if a different method will be called.
Hope that helps!
I am trying to use PHP closures for the first time.
I wrote a small function that will take an array and a function in it's parameter. Its job is to loop through the giving array and it executes the $function on each element.
Here is my function
/**
* It check each item in a giving array for a property called 'controllers',
* when exists it executes the $handler method on it
*
* #param array $items
* #param function $handler
*/
protected function addSubControls($items, $handler)
{
foreach( $items as $item){
if( property_exists($item, 'controllers')){
//At this point we know this item has a sub controller listed under it, add it to the list
foreach($item->controllers as $subControl){
$handler( $subControl );
}
}
}
}
Now I want to use this function in 2 ways.
First: execute the method generateHtmlValues() on every item within the giving array. This works with no issues.
$this->addSubControls($control->items, function($subControl){
$this->generateHtmlValues( $subControl );
});
Second: I want to add each qualifying item to an array that is being used outside that closure method.
$controls = ['a','b','c'];
$this->addSubControls($control->items, function($subControl) use(&$controls) {
$controls[] = $subControl->id;
});
var_dump($controls);
at this point I am expecting that the $controls array to have 1 more value that what the original one is set to. But it is not doing that.
What am I missing here? How can the closure populate the array that I pass by reference?
After all, my code works correctly.
I was looking at the wrong output.
I will keep this question hoping it will help someone else.
I wrote a vcard class with Phalcon in PHP. The vCard Model is initialized like this.
// Inside the BS_VCard class
public function initialize(){
$this->hasMany("id","BS_VCardElement","vCardId",array(
"alias" => "elements",
'foreignKey' => array(
'action' => Phalcon\Mvc\Model\Relation::ACTION_CASCADE
)
));
}
Its elements are initialized like this
// Inside the BS_VCardElement class
public function initialize(){
$this->belongsTo("vCardId","BS_VCard","id",array("alias" => "vCard"));
...
}
If a user reads a vCard and adds another element, it doesn't work as expected. To simplify the use I added some fascade methods like this
public function addDateOfBirth($date){
$element = new BS_VCardElement();
$element->setName("BDAY");
$element->addValue($date);
// This doesn't work
$this->elements[] = $element;
}
The Docs/Storing related records do not explain how to append fresh data like this to the related table.
I also tried this
$this->elements[] = array_merge($this->elements,array($element));
But the save method seems to ignore the added element. Save() returns true.
This question has been asked a couple of months ago but since I ran into a similar issue I decided to share my results anyway.
And here's what I found. Lower case aliases ('elements') don't seem to work whereas upper case aliases ('Elements') do.
To add one element you can do this;
$this->Elements = $element;
To add multiple elements you can do this;
$elements = array($element1, $element2);
$this->Elements = $elements;
After that you have to save the vcard before accessing the elements again. If you don't, phalcon will just return a result set with only the elements already in the database. (Not sure if this can be changed somehow.)
And here's the documentation (where all this is not mentioned): http://docs.phalconphp.com/en/latest/reference/models.html#storing-related-records
According to the Phalcon source code, the Resultset object is immutible.
/**
* Resultsets cannot be changed. It has only been implemented to
* meet the definition of the ArrayAccess interface
*
* #param int index
* #param \Phalcon\Mvc\ModelInterface value
*/
public function offsetSet(var index, var value)
{
throw new Exception("Cursor is an immutable ArrayAccess object");
}
It appears that replacing the element with an array is the only way to implement an "append" or modification of the resultset (other than delete which IS supported).
Of course this breaks the \Phalcon\Mvc\Model::_preSaveRelatedRecords() because the function ignores the class properties and refetches the related from the Model Manager (and resets the model::$element attribute at the end).
I feel frustrated by this because appending objects to a collection seems like a very common task and not having a clear method in which to add new items to a parent seems like a design flaw.
I think related elements might have some magic functionality invoked when you set the properties, so simply using $this->elements[] (evidently) doesn't work. Perhaps try re-setting the entire variable:
public function addDateOfBirth($date){
$element = new BS_VCardElement();
$element->setName("BDAY");
$element->addValue($date);
$elements = $this->elements;
$elements[] = $element;
$this->elements = $elements;
}
One question in short: can phpunit use multiple data provider when running test?
For example, I have a method called getById, and I need to run both successful and unsuccessful testcases for it.
The successful testcases means that it can return a corresponding record. And for the unsuccessful, the input can fall in two categories: invalid and failed.
The invalid means that the input is not legal, while failed means the input could be valid, but there is no corresponding record with that ID.
So the code goes like this:
/**
* #dataProvider provideInvalidId
* #dataProvider provideFailedId
*/
public function testGetByIdUnsuccess($id)
{
$this->assertNull($this->model->getById($id));
}
But it turned out that only the first data provider has been used, ignoring the second one. Though I am not sure this senario is common or not, but here is the question. Can we use multiple data provider? And if we can, how?
PS: did not find too much help in here
Just an update to the question, a pull request was accepted and now the code:
/**
* #dataProvider provideInvalidId
* #dataProvider provideFailedId
*/
public function testGetByIdUnsuccess($id)
{
$this->assertNull($this->model->getById($id));
}
Will work on PHPUnit 5.7, you'll be able to add as many providers as you want.
You can use a helper function as shown below. The only problem is if the total number of test cases provided by all "sub data providers" is large, it can be tedious to figure out which test case is causing a problem.
/**
* #dataProvider allIds
*/
public function testGetByIdUnsuccess($id)
{
$this->assertNull($this->model->getById($id));
}
public function allIds()
{
return array_merge(provideInvalidId(),provideFailedId());
}
You can also use CrossDataProviders which allows you to use a combination of data providers with each other
<?php
/**
* #dataProvider provideInvalidIdAndValues
*/
public function testGetByIdUnsuccess($id, $value)
{
$this->assertNull($this->model->getById($id));
}
function provideInvalidIdAndValues() {
return DataProviders::cross(
[[1], [2], [3]],
[['Rob'], ['John'], ['Dennis']]
);
}
You can add a comment to your dataProvider array, to provide the same functionality, while not requiring multiple dataProviders.
public static function DataProvider()
{
return array(
'Invalid Id' => array(123),
'Failed Id' => array(321),
'Id Not Provided' => array(NULL),
);
}
well,you could consider it from another side ;)
you know exactly what is your expected,for example getById(1) expected result is $result_expected,not $result_null
so,you could make a dataprovider like this
$dataProvider = array(1, 'unexpected');
then,your test method like this:
public function testGetById($id) {
$this->assertEquals($result_expected, $obj::getById($id));
}
so,test result is:
.F
I want to fetch a method's comments,take below method for example:
/**
* Returns the regex to extract all inputs from a file.
* #param string The class name to search for.
* #return string The regex.
*/
public function method($param)
{
//...
}
the result should be
Returns the regex to extract all inputs from a file.
#param string The class name to search for.
#return string The regex.
the way I find is use a function like file_get_content to get file content -> filter the method I want -> fetch the comment use regexp
it seems a bit complicated , is there any convenient way to archive this?
actually you can get a method's doc comments with getDocComment
$ref=new ReflectionMethod('className', 'methodName');
echo $ref->getDocComment();
If you want to use the comment in PHP for something check out getDocComment in php's reflection api
PHP Doc. Like Java Doc.
For a method dump I use this little function I composed.
It fetches all methods from provided class that are public(and thus of use to you).
I personally use a dump() method to nicely format the outputted array of method names and descriptions, but that's not needed if you wish to use it for something else :-)
function getDocumentation($inspectclass) {
/** Get a list of all methods */
$methods = get_class_methods($inspectclass);
/** Get the class name */
$class =get_class($inspectclass);
$arr = [];
foreach($methods as $method) {
$ref=new ReflectionMethod( $class, $method);
/** No use getting private methods */
if($ref->isPublic()) {
$arr[$method] = $ref->getDocComment();
}
}
/** dump is a formatting function I use, feel free to use your own */
return dump($arr);
}
echo getDocumentation($this);