Ok so I am testing my ajax callbacks for my wordpress plugin.
So I basically followed instructions here
https://codesymphony.co/wp-ajax-plugin-unit-testing/
Here is my ajax callback function
public function my_plugin_get_site_pages( $args = null ) {
//...... Processing $site_pages.....
$response = array(
'status' => 'success',
'site_pages' => $site_pages
);
#header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
echo wp_json_encode( $response );
wp_die();
}
Here is my test
class My_Plugin_Ajax_Test extends WP_Ajax_UnitTestCase {
private $_foo;
public function setup() {
//.....Initialize $_foo here...
}
public function test_foo() {
try {
$_POST[ 'args' ] = array( 'return_format' => 'raw' );
add_action( 'wp_ajax_my_plugin_get_site_pages' , array( $this->_foo , 'my_plugin_get_site_pages' ) );
//$this->setExpectedException( 'WPAjaxDieStopException' );
$this->_handleAjax( 'my_plugin_get_site_pages' );
} catch ( WPAjaxDieStopException $e ) {}
//$response = json_decode( $this->_last_response );
$response = $this->_last_response;
var_dump( $response );
}
}
Now here are the issues
It doesn't throw WPAjaxDieStopException exception like its suppose to
when I do this code $this->setExpectedException( 'WPAjaxDieStopException' );
it fails the test https://snag.gy/JSTqHV.jpg
It prints out that wp_die() has been triggered, so this code
$response = $this->_last_response;
var_dump( $response );
prints this
https://snag.gy/pKqfUk.jpg
Number 2 is an issue because you cannot do json_decode the string outputted coz its an invalid json string, so I can't continue with my test.
I'm just starting out with automated testing on wordpress plugins and I appreciate any help.
Note:
My ajax callback is working ok on my live plugin even if I use wp_die(), it just prints that weird 'wp_die called ...' string on my test.
My php version is 5.6.21 and my phpunit version is 4.8.26
Here are some additional info
So both 'WPAjaxDieStopException' and 'WPAjaxDieContinueException' are not thrown,
however what's interesting is when I do this
$this->_setRole( 'administrator' );
I get this error on the console
Trying to get property of non-object
/tmp/wordpress-tests-lib/includes/testcase-ajax.php:151
/vagrant/www/wordpress/wp-content/plugins/my-plugin/tests/test-file.php:30
But clearly I'm extending WP_Ajax_UnitTestCase and it has the _setRole method
https://core.trac.wordpress.org/browser/trunk/tests/phpunit/includes/testcase-ajax.php#L168
Also when I run phpunit I get this bunch of errors or warnings on the console
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
WordPress database error Duplicate key name 'location_type_code' for query ALTER TABLE wptests_woocommerce_tax_rate_locations ADD KEY location_type_code (location_type(40),location_code(90)) made by PHPUnit_TextUI_Command::main, PHPUnit_TextUI_Command->run, PHPUnit_TextUI_Command->handleArguments, PHPUnit_TextUI_Command->handleBootstrap, PHPUnit_Util_Fileloader::checkAndLoad, PHPUnit_Util_Fileloader::load, include_once('/vagrant/www/wordpress/wp-content/plugins/my-plugin/tests/bootstrap.php'), require('/tmp/wordpress-tests-lib/includes/bootstrap.php'), require_once('wp-settings.php'), do_action('init'), call_user_func_array, WC_Install::check_version, WC_Install::install, WC_Install::create_tables, dbDelta
Also I am using vagrant and use http://vccw.cc/ for my dev env and also following this guide on adding tests for woocommerce extensions
https://github.com/Automattic/wc-extensions-code-test-guide
Hope all this additional info will help in finally solving this issue.
Been away for a while, been very busy, finally got some time to figure this out. It turns out it's a stupid mistake (facepalm).
Since we are using WP_Ajax_UnitTestCase which extends WP_UnitTestCase
Then when we use function setup in WP_Ajax_UnitTestCase then we need to call this parent::setup();
public function setup() {
parent::setup();
// your init codes here
}
I was not calling that on my existing code that's why the test is acting weird. Adding that solves all the weird issues and runs the test as expected and throws necessary exceptions.
WPAjaxDieStopException is thrown if ajax callback function does not yield any output.
WPAjaxDieContinueException is thrown if ajax callback yields any output.
Also make sure to use wp_die() instead of die() on your ajax callback, if you use the later, the test will halt.
I am planning to write and extensive guide to doing automated testing in WordPress plugins, I'll put the link to it here soon.
For now I hope this helps anyone.
Related
I'm new to PHPUnit and wondering is it possible to write a test for which ignore specific method.
The code is like examine whether $data is Valid or not, and if it find irregular data, send message to slack with it.
My question is, is it possible to run a test without sending alert message, like ignore sendAlert function?
If possible, I want to know how to write it, If not, I want know why and how to make this code testable.
Thanks!!
example code )
public static function isValid($data) {
// some code here
if (Valid) {
return true;
} else {
// some code here to find irregular
if (irregular) {
self::sendAlert($data);
}
return false;
}
}
private static function sendAlert($data) {
// send alert to slack
Example_Model_Slack::post($slackMsg, $channel);
}
<?
class Example_Model_Slack
{
public static function post($text, $channel = '') {
// make $params from $text and $channel
// POST
$stream = [
'http' => [
'method' => 'POST',
'protocol_version' => 1.1,
'content' => http_build_query($params),
],
];
return file_get_contents(self::POST_URL, false, stream_context_create($stream));
}
}
Edit after the question edit
If your code is in a namespace (which should be, it's good practice), it's extremely easy:
Create a new function in a separate file that is only included by your UnitTest file. This file should have the same namespace as your code. In this example, Example_Model_Slack is in the namespace Foobar\Models.
<?php
namespace Foobar\Models;
function file_get_contents(string $filename, bool $use_include_path = false, resource $context = ?)
{
return 'Whatever you want';
}
When you call a function, the code looks for it:
In the specifically used functions.
In the same namespace.
In the built-in functions.
Therefore, your code will use the built-in file_get_contents (namely \file_get_contents), but your test will use the one in the same namespace (namely \Foobar\Models\file_get_contents).
Original answer
The easiest would be to actually call sendAlert, but to mock the call to its content. As you didn't provide the code of that method, I can't be more precise, juste browse through the doc and figure it out by yourself or, alternatively, show us the code.
For a theorectical and general answer: your sendAlert method probably uses one that is provided by an external vendor, let's say \SlackApi\Slack::send($message). In that case, you could mock the provided \SlackApi\Slack class to replace the send method with one that doesn't actually send anything but still returns the expected data.
I'm building a way to extend a WordPress plugin I'm developing using the following filter for grabbing the html content from a different plugin:
$content = apply_filters('satl_render_view', array($view, $slides));
With just one plugin this works perfectly, but once I activate a second plugin utilizing this same filter it stops working, $content is null for either plugin:
I'm adding the filters on the plugins in the __construct() method:
add_filter('satl_render_view', array('SatellitePortraitPlugin','addRender'));
and
add_filter('satl_render_view', array('SatelliteAwesomePlugin', 'addRender'));
Anyone run into this before?
In case it helps, this is the addRender method as it currently stands:
public static function addRender($params)
{
list($view, $slides) = $params;
$plugin = new SatelliteAwesomePlugin();
return $plugin->render($view, array('slides' => $slides, 'frompost' => 'false'), false);
}
For the record, I've tried remove_filter() if there is no content to return, but that didn't solve the problem.
Params get passed to callback function, in this case addRender() and contain all the HTML of what is wanted to display to the second plugin using that same filter. To utilize this bit of information one must change the method:
public static function addRender($params)
{
if (is_array($params)) {
list($view, $slides) = $params;
$plugin = new SatelliteAwesomePlugin();
return $plugin->render($view, array('slides' => $slides, 'frompost' => 'false'), false);
} else {
return $params;
}
}
You also need to make sure to update how the render() method passed the proper information back to the apply_filters method so the next plugin would have the proper array to run the addRender()
} else {
return array($file,$params['slides']);
}
Main Learning: WordPress apply_filters is much dumber than you'd think. You can't assume it merges everything for you, it just passes the information along and your code needs to make sense of it.
Just upgraded Mediawiki 1.19.6 to the most current, 1.22.2.
Used update.php, which worked just fine. The front page loads, as do SOME of the articles if you enter their exact URL. However, following any of the links produces:
Catchable fatal error: Argument 1 passed to
ContentHandler::getContentText() must implement interface Content,
boolean given, called in <wiki path>/includes/Article.php on line 389
and defined in <wiki path>/includes/content/ContentHandler.php on line
95.
I've looked up the call to getContentText() in Article.php, and it's in a function called fetchContent(), with a comment about it being crufty and a note that the ContentHandler method within is deprecated.
I can't figure out how to fix what's gone wrong, and web searches are only turning up bug reports that are marked fixed... any ideas? Thanks very much.
getContentText() is depreciated.
Use WikiPage::getContent()
https://doc.wikimedia.org/mediawiki-core/master/php/html/classArticle.html#affd3b52d2544cc334d7805ae9e5aba98
We had the same problem. Our ICT guy handled it by adapting the Article.php file placed in the includes directory of your mediawiki. Only 1 function was adapted (line 377 function function fetchContent()). I do not know the exact working principle but the MediaWiki returned to normal.
Also I believe you need to run the mediawiki update routine by visiting:
'HostAdress'/MediaWiki/mw-config/
Original function in Article.php:
function fetchContent() { #BC cruft!
ContentHandler::deprecated( __METHOD__, '1.21' );
if ( $this->mContentLoaded && $this->mContent ) {
return $this->mContent;
}
wfProfileIn( __METHOD__ );
$content = $this->fetchContentObject();
// #todo Get rid of mContent everywhere!
$this->mContent = ContentHandler::getContentText( $content );
ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
wfProfileOut( __METHOD__ );
return $this->mContent;
}
New function in Article.php:
function fetchContent() { #BC cruft!
ContentHandler::deprecated( __METHOD__, '1.21' );
if ( $this->mContentLoaded && $this->mContent ) {
return $this->mContent;
}
wfProfileIn( __METHOD__ );
$content = $this->fetchContentObject();
if ( !$content ) {
wfProfileOut( __METHOD__ );
return false;
}
// #todo Get rid of mContent everywhere!
$this->mContent = ContentHandler::getContentText( $content );
ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
wfProfileOut( __METHOD__ );
return $this->mContent;
}
I'm trying to write my first unit test in cakePHP.
I would like to try it out with the following action:
public function joinLobby($userId = null, $lobbyId = null) {
// json webservice layout
$this->layout = 'ajax';
if (!$userId || !$lobbyId) {
throw new CakeException(__('Paramter(s) missing'));
}
if (!$this->Lobby->exists($lobbyId)) {
throw new CakeException(__('Invalid lobby supplied'));
}
if (!$this->User->exists($userId)) {
throw new CakeException(__('Invalid user supplied'));
}
$this->__addUsersToLobby(array($userId), $lobbyId);
}
And my test case goes like this so far:
public function testShouldNotAddNonExistentUserToLobby() {
$this->LobbiesController = $this->generate('Lobbies', array(
'methods' => array(
'joinLobby',
'__addUsersToLobby'
),
'models' => array(
'Lobby',
'User'
)
));
$this->LobbiesController->Lobby->expects($this->any())->method('exists')->will($this->returnValue(true));
$this->LobbiesController->User->expects($this->once())->method('exists')->will($this->returnValue(false));
$this->LobbiesController->expects($this->never())->method('__addUsersToLobby');
$this->testAction('Lobbies/joinLobby/1/1');
}
When I run my test, I get this:
UsersLobbiesControllerTest::testShouldNotAddNonExistentUserToLobby
Expectation failed for method name is equal to when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
The only way I can get the test to pass is by changing both "$this->once()" to "$this->any()" or "$this->never()".
Any help??
Thanks in advance!
I've finally managed to make it work.
The problem was that I was mocking the same function I was testing, so it wasn't actually called. Therefore, the code that called the other functions wasn't executed.
Removing 'joinLobby' from the 'methods' array made the work.
Cheers
I have been working on a little MVC project to assist in my self-learning and I have come across an issue that completely baffled me. I made a blog section in this MVC-ish system and pulled user permissions from an ACL with no problem whatsoever.
I moved onto creating a member section and as soon as i added any permissions checking I get the following error from Chrome:
No data received
Unable to load the web page because the server sent no data.
Here are some suggestions:
Reload this web page later.
Error 324 (net::ERR_EMPTY_RESPONSE): The server closed the connection without sending any data.
I thought it was weird, so I double checked my error logs and nothing had shown up. So I decided to copy and paste the working blog code into the member file, reloaded and i got the EXACT same error, the only difference between the two files right now is the file name and the class name.
Here is the Blog code:
<?php
class blog extends frontController {
public $model;
public $user;
public function __construct()
{
parent::__construct();
$this->model = $this->autoload_model();
$this->user = $this->load_user();
$this->user->getUserRoles();
}
public function index()
{
//Will only list the latest post ;)
if(!$this->user->hasPermission('blog_access'))
{
$array = $this->model->list_posts();
if(empty($array))
{
$this->variables(array(
'site_title' => 'View Blog Posts',
'post_title' => 'Sorry but there are no posts to display'
));
} else {
$this->variables(array(
'site_title' => 'View Blog Posts',
'list' => $array[0],
'post_title' => $array[0]['entry_title'],
'link' => str_replace(' ', '_',$array[0]['entry_title']),
));
}
} else {
$this->variables(array(
'site_title' => 'Error :: Design Develop Realize',
'body' => 'Sorry, but you do not have permission to access this',
));
}
$this->parse('blog/list', $this->toParse);
}
This is the member file:
<?php
class member extends frontController {
public $model;
public $user;
public function __construct()
{
parent::__construct();
$this->model = $this->autoload_model();
$this->user = $this->load_user();
$this->user->getUserRoles();
}
public function index()
{
//Will only list the latest post ;)
if(!$this->user->hasPermission('blog_access'))
{
//$array = $this->model->list_posts();
if(empty($array))
{
$this->variables(array(
'site_title' => 'Design Develop Realize :: View Blog Posts',
'post_title' => 'Sorry but there are no posts to display'
));
} else {
$this->variables(array(
'site_title' => 'Design Develop Realize :: View Blog Posts',
'list' => $array[0],
'post_title' => $array[0]['entry_title'],
'link' => str_replace(' ', '_',$array[0]['entry_title']),
));
}
} else {
$this->variables(array(
'site_title' => 'Error :: Design Develop Realize',
'body' => 'Sorry, but you do not have permission to access this',
));
}
$this->parse('blog/list', $this->toParse);
}
In the member class, if I comment out $this->user = $this->load_user(); then the error disappears!!!
Just for reference here is that function:
protected function load_user()
{
if(!$this->loader->loaded['acl'])
{
$this->loader->loadCore('acl');
}
return $this->loader->loaded['acl'];
}
Any help or suggestions would be appreciated as I am stumped!
PS yes I do have error reporting set to cover everything and no it does not log anything!
EDIT: Because all files go through index.php I have placed the error reporting there:
<?php
error_reporting(E_ALL);
ini_set('date.timezone', "Europe/London");
require_once('system/library/loader.php');
$loader = new loader();
$loader->loadCore(array('frontController', 'routing'));
EDIT 2: loadCore() is below
public function loadCore($toLoad, $params = false)
{
//important task first, check if it is more then 1 or not
if(is_array($toLoad))
{
//more then one so lets go to the task!
foreach($toLoad as $file)
{
if(file_exists('system/library/' . $file . '.php'))
{
require_once('system/library/' . $file . '.php');
if($params)
{
$this->loaded[$file] = new $file($params);
} else {
$this->loaded[$file] = new $file;
}
} else {
trigger_error("Core File $file does not exist");
}
}
} else {
//Phew, less work, it is only one!
if(file_exists('system/library/' . $toLoad . '.php'))
{
require_once('system/library/' . $toLoad . '.php');
if($params)
{
echo(__LINE__); exit;
$this->loaded[$toLoad] = new $toLoad($params);
} else {
$this->loaded[$toLoad] = new $toLoad;
}
}
}
}
Update: I modified loadCore so that if it was the acl being called it would use a try...catch() and that has not helped as it will not display an error just the same chrome and IE pages
Update 2: I have spoken with my host and it seems that everytime this error occurs, apache logs the following (not sure why I cannot see it in my copy of the logs!)
[Wed Feb 22 08:07:11 2012] [error] [client 93.97.245.13] Premature end
of script headers: index.php
You have commented out
//$array = $this->model->list_posts();
So now array is null and you are trying to use
'list' => $array[0],
'post_title' => $array[0]['entry_title'],
which definitely will generate an error.
EDIT:-
I see you have
'body' => 'Sorry, but you do not have permission to access this' , )); }
That is in second else which is a syntax error. and generates an output. If you have enabled, compress output in CI, that will cause this error.
"Premature end of script headers" are internal server errors. Which generally occurs when script breaks and does not send any HTTP headers before send the error messages. There might be several causes to this.
One might be output buffering. May be the server you are using buffers the output by default. I will suggest turning off the output_buffering using output_buffering = off on php.ini [docs here].
Make sure you are sending correct HTTP headers also
print "Content-type: text/html\n\n";
There are few more suggestion on this link.
To learn more about this error, go here.
Hope it helps
I'd be interested in seeing what's inside the loadCore function.
Have you used error_log anywhere? It might help shed some light on the issue.
Have you tested this page in different browsers? Googled your error, and it's indicating that it may be chrome specific?
Your statement that commenting out the loadCore('acl') is interesting, so obviously I would start there. You're sure that it is getting the system/library/acl.php page via the loader? Aka, it is triggering that line 2 below the exit in loadCore()? var_dump the return imo to make sure you're getting the object.
First of all. This error is produced when your server disconnect before it send any data.
A few suggestions.
your code is broken and let crash the application
your error logging should be enhanced by NOTICES
write tests to check every part
u should enable and use a debugger (zend_debugger, xdebug)
post a few more connection infos (e.g. wget -O - --debug 'url')
There is/was a special chrome issue for that problem
http://www.google.pl/support/forum/p/Chrome/thread?tid=3aa7b40eb01a95c8&hl=en#fid_3aa7b40eb01a95c80004ae797939c267
I suggest checking whether there are any blank lines before the tags.
What file names are you using?, are there any conventions you have to follow to fit a framework you might be using?
I wonder if this is a server problem and not a code problem?
This thread suggests that the server error you are seeing (500 premature end to headers) could be the result of Apache logs that are too full and need to be rotated. However it really could be anything - it seems to suggest simply that the script returns no output at all to the browser.
The other thing I'd check is file permissions, and the functioning of the include_once and file_exists in the loadCore method, as that looks the likeliest area to cause problems that might stop the script without even throwing a php error.
Maybe it´s a character/encoding error, open both files with notepad++ , goto Encoding, then select Convert to UTF-8, save the file and test it again.
Good Luck!