I'm having a problem. I have a unit test that is trying to access methods from a class. My unit test is as follows
public function testReverseArraySuccess()
{
$data = "This is a test.";
$output = (new Reverse)
->setInput($data)
->get();
$this->assertEquals(['test', 'a', 'is', 'This'], $output);
}
My class is a follows
class Reverse
{
public $input;
/**
*
* #param $data
*/
public function setInput($data) {
$this->input = $data;
}
/**
*
*#return void
*/
public function get()
{
return $this->input;
}
}
How can I setup my class so that my Test will pass?
The error I get when I run the test is.
Error : Call to a member function get() on null
setInput isn't chainable since it's not returning an instance of itself. It's a void function that returns null by default that's why you're getting that error message. Update setInput to return an instance of the class to make it chainable.
public function setInput($data) {
$this->input = $data;
return $this;
}
Now get will return whatever value is passed to the input property, here's a working demo.
Then it's a matter of reversing the input, but I'll leave that up OP. 👍
You need to call get data directly after set data by instance of Reverse class.
public function testReverseArraySuccess() {
$data = "This is a test.";
$output = new Reverse;
$output->setInput($data);
$this->assertEquals(['test', 'a', 'is', 'This'], $output->get());
}
Related
I have a class Receipt.php
<?php
namespace TDD;
class Receipt {
private $user_id = 1;
private $pending_amount;
public function total(array $items = []){
$items[] = $this->pending();
return array_sum($items);
}
public function tax($amount,$tax){
return $amount * $tax;
}
private function pending()
{
$sql = 'select pending_amount from Pending_transtions where user_id =' . $this->user_id . ' limit 1;';
//$pending_amt = $this->mainDb->get_sql_row($sql);
//$this->pending = $pending_amt['pending_amount'];
return $this->pending_amount = 45;
}
public function addTaxPending($tax){
return $this->pending_amount * $tax;
}
}?>
And in my PHPUnit file, ReceiptTest.php
<?php
namespace TDD\Test;
require(dirname(dirname(__FILE__))).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php';
use PHPUnit\Framework\TestCase;
use TDD\Receipt;
class ReceiptTest extends TestCase{
public function setUp(): void {
$this->Receipt = new Receipt();
}
public function tearDown(): void{
unset($this->Receipt);
}
public function testTotal(){
$input = [0,2,5,8];
$output = $this->Receipt->total($input);
$this->assertEquals(15,$output,"this is not valid");
}
public function testTax(){
$inputAmount = 10.00;
$inputTax = 0.10;
$output = $this->Receipt->tax($inputAmount,$inputTax);
$this->assertEquals(1.0,$output,"this tax expecting 1.0");
}
}?>
question:
How to ignore internal calling function pending() because it fetches data from DB. At the same time I want to access the property of $this->pending_amount.
Here Pending() must be private function.
How can I achieve that? I am looking for your valuable solutions
Proper solution is to replace your dependency (the one which is saved under $this->mainDb in your example) with a "mocked" one in your test case.
Here is an article from PHPUnit manual, which shows how to create mocks - https://phpunit.readthedocs.io/en/9.5/test-doubles.html#mock-objects
--
Speaking about ways of injection: your can either pass $this->mainDb instance via class constructor, or make so-called "seam" in form of public setMainDb method (which is not an elegant solution - I'd prefer to avoid it).
Another thing which I had to do sometimes, is to replace the value via Reflection: make private property accessible and set it inside of test to the value I need.
--
Update:
Based on given example, I think the easiest way to achieve desired result is:
Change test case's setUp to:
public function setUp(): void
{
$this->Receipt = new Receipt();
$mainDbMock = new class() {
public function get_sql_row() {
return [
"pending_amount" => 0
];
}
};
$this->Receipt->setMainDb($mainDbMock);
}
Add "seam"-method to your Receipt class:
public function setMainDb($mainDb)
{
$this->mainDb = $mainDb;
}
You need to mock the dependency. The dependency in your case is the database. You need to replace access to the database with a mock that you can configure with known return values.
Take the time to read the PHPUnit documentation on how to use mock objects.
A basic example
Given a DB access class with a get_sql_row method that accepts an SQL string and returns some data structure, we don't need to fill in any code here in our example because we're going to mock the get_sql_row method and configure it to return known values for our test.
class MainDb
{
public function get_sql_row(string $sql)
{
// ...
}
}
Our receipt class accepts a DB access object in the constructor and uses it to execute SQL e.g. the pending method executes SQL to get the pending amount (if any).
class Receipt
{
public function __construct($mainDb)
{
$this->mainDb = $mainDb;
}
public function total(array $items = [])
{
$items[] = $this->pending();
return array_sum($items);
}
private function pending()
{
$pendingAmount = $this->mainDb->get_sql_row('sql...');
return $pendingAmount['pending_amount'];
}
}
In the testcase setup method we create a mock of the DB access class and create the receipt object with the mocked database. The mock is then used in the tests to configure expectations, received arguments, and return values.
The testTotalWithNoPendingAmount test configures the DB mock to return 0 when get_sql_row is invoked. The get_sql_row is only expected to be called once and will fail otherwise. Configuring get_sql_row to return a known value allows us to test the behaviour of the receipt class without touching the DB.
Similarly, testTotalWithPendingAmount configures the DB mock get_sql_row to return a value other than 0 so that we can test behaviour of calculating the total when there is a pending amount.
final class ReceiptTest extends TestCase
{
protected Receipt $receipt;
protected function setUp(): void
{
// create a mock of the database access object
$this->dbMock = $this->createMock(MainDb::class);
// create receipt with mocked database
$this->receipt = new Receipt($this->dbMock);
}
public function testTotalWithNoPendingAmount(): void
{
$this->dbMock->expects($this->once())
->method('get_sql_row')
->willReturn(['pending_amount' => 0]);
$this->assertEquals(15, $this->receipt->total([0, 2, 5, 8]));
}
public function testTotalWithPendingAmount(): void
{
$this->dbMock->expects($this->once())
->method('get_sql_row')
->willReturn(['pending_amount' => 3]);
$this->assertEquals(18, $this->receipt->total([0, 2, 5, 8]));
}
}
Take the time to read the PHPUnit documentation on using mock objects and understand how they work and how to use them in your testing.
Note also that your handling of SQL is potentially open to SQL injection. Please read about SQL injection.
Got a stucking situation which produces unnecessary IDE warnings and may lead to cleaning-up used code.
I think the best solution is to add some PHPDoc at the right place, but couldn't find the right place yet because of some constraints, as explained in the below examples.
IDE: PhpStorm
Result:
<?php
/*
* Generic result class, widely used, so can't touch it ¯\_(ツ)_/¯
*/
class Result {
public $data;
public function __construct() {
return $this;
}
public function setData($data) {
$this->data = $data;
return $this;
}
}
Customer:
<?php
class Customer {
public string $name = '';
public string $email = '';
public function __construct() {
$this->name = 'John Smith';
$this->email = 'test#example.com';
}
public function getCustomer(): Result {
return (new Result())->setData(new self());
}
public function reverseName(): string { // ❌ Unused element: 'reverseName'
$parts = explode(' ', $this->name);
return implode(' ', array_reverse($parts));
}
}
Controller:
<?php
require_once 'vendor/autoload.php';
$john = (new Customer())->getCustomer();
// ℹ️ $john->data is an instance of the Customer class.
// How should I tell to the IDE this ^ ❓
$reversedName = $john->data->reverseName(); // ❌ Cannot find declaration to go when trying to navigate to the method
exit($reversedName);
Tried many and many options, but the only one which works is by adding a PHPDoc to Result's $data property. Can't touch it because it's widely used in the project...
LE:: The Customer class has a lot of methods similar to reverseName(), so assigning the data property to a new variable is also difficult to write: /** #var Customer $john */ $john = (new Customer())->getCustomer()->data;
You could try to use another PhpStorm feature called advanced metadata.
Firstly you need to configure metadata in the next way
namespace PHPSTORM_META {
override(\App\Result::getData(0), map(['' => '#']));
}
then add getter to your Result class
public function getData()
{
return $this->data;
}
and use this getter in the next way
$john->getData(Customer::class)->reverseName();
The main idea is to use class name as an argument of the getter method and PhpStorm will know what class object this getter will return.
I have some problems with my Laravel app. I'm trying to make scraper and It looks like this.
<?php
namespace App\Http\Controllers;
use App\Scraper\Amazon;
use Illuminate\Http\Request;
class ScraperController extends Controller
{
public $test;
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$this->test = new Amazon();
return $this->test;
}
As you can see I have created new folder inside app that is called Scraper, and I have Amazon.php file that looks like:
<?php
namespace App\Scraper;
use Goutte\Client;
class Amazon
{
public string $test = '';
public function __construct()
{
return "test";
}
public function index()
{
$client = new Client();
$crawler = $client->request('GET', 'https://www.amazon.co.uk/dp/B002SZEOLG/');
$crawler->filter('#productTitle')->each(function ($node) {
$this->test = $node->text()."\n";
});
return $this->test;
}
}
And this is always returning error like
TypeError: Argument 1 passed to
Symfony\Component\HttpFoundation\Response::setContent() must be of the
type string or null, object given,
What am I doing wrong?
I believe the problem is returning the object itself (return $this->test), you should use return response()->json([$this->test]);
return var_dump($node->text()); before return $this->test to be sure test() is returning the expected value.
In my case I had a post request to a function
$contest = Contest::with('contestRatings')->where('id', '=', $contest_id);
return $contest;
I needed get() to make it work
$contest = Contest::with('contestRatings')->where('id', '=', $contest_id)->get();
This issue resolved for me when I added ->get().
Try:
return $this->test->get();
Use this syntax to return your response.
return response()->json($this->test);
I have a unit test class in which I want to instantiate a object from another class in order to that I used setUpBeforeClass() fixtures of phpunit. So if I will use that recently instantiated object directly in test function then its working fine.
If i'll use this object into another function which had been created for data providers. So that object sets to null cause providers always execute first.
Is there a way to call dataProviders just before the test runs, instead?
require_once('Dashboard.php');
Class Someclass extends PHPUnit_Framework_TestCase {
protected static $_dashboard;
public static function setUpBeforeClass()
{
self::$_dashboard = new Dashboard();
self::$_dashboard->set_class_type('Member');
}
/**
* Test Org Thumb Image Existense
* param org profile image : array
* #dataProvider getOrgProfileImages
*/
public function testFieldValidation($a,$b){
//If I call that object function here it will give the result.
//$members = self::$_dashboard->get_members();
//var_dump($members); Printing result as expected
$this->assertTrue(true);
}
public function getOrgProfileImages() : array {
//var_dump(self::$_dashboard);
$members = self::$_dashboard->get_members();
$tmp_array = ['2','2'];
return $tmp_array;
}
public static function tearDownAfterClass()
{
self::$_dashboard = null;
}
}
Error:
The data provider specified for Someclass::testFieldValidation is invalid.
Call to a member function get_members() on null
Please help to mitigate this issue.
Note: since I don't have the source of your Dashboard class, I'm using a random number in the examples below instead
Providers are invoked before any tests are run (and before any hooks, including beforeClass have a chance to run). By far the easiest way to achieve what you're after is to populate that static property on the class load:
use PHPUnit\Framework\TestCase;
/** #runTestsInSeparateProcesses enabled */
class SomeTest extends TestCase
{
public static $_rand = null;
public function provider()
{
$rand = self::$_rand;
var_dump(__METHOD__, getmypid(), 'provided rand', $rand);
return ['rand' => [$rand]];
}
/** #dataProvider provider */
public function testSomething($rand)
{
$this->expectNotToPerformAssertions();
var_dump(__METHOD__, getmypid(), 'tested with', $rand);
}
/** #dataProvider provider */
public function testSomethingElse($rand)
{
$this->expectNotToPerformAssertions();
var_dump(__METHOD__, getmypid(), 'tested with', $rand);
}
}
// this runs before anything happens to the test case class
// even before providers are invoked
SomeTest::$_rand = rand();
Or you could instantiate you dashboard in the provider itself, on the first call:
public function provider()
{
// Instantiate once
if (null === self::$_rand) {
self::$_rand = rand();
}
$rand = self::$_rand;
var_dump(__METHOD__, getmypid(), 'provided rand', $rand);
return ['rand' => [$rand]];
}
#dirk-scholten is right. You SHOULD be creating a new object for each test. It's a GOOD testing practice. Frankly it looks more like you are testing the data and not testing the code, which is fine I guess, it's just not the typical use of PHPUnit. Based on the assumption that you want to make sure every user in the database has a thumbnail image (just guessing), I would go with the following:
<?php
class DashboardDataTest extends PHPUnit\Framework\TestCase {
private $dashboard;
public function setUp() {
$this->dashboard = new Dashboard();
}
/**
* Test Org Thumb Image Existence
* param org profile image : array
*
* #dataProvider getOrgProfileImages
*
* #param int $user_id
*/
public function testThumbnailImageExists(int $user_id){
$thumbnail = $this->dashboard->get_member_thumbnail($user_id);
$this->assertNotNull($thumbnail);
}
public function geOrgUserIDs() : array {
$dashboard = new Dashboard();
// Something that is slow
$user_ids = $dashboard->get_all_the_member_user_ids();
$data = [];
foreach($user_ids as $user_id){
$data[] = [$user_id];
}
return $data;
}
}
Each data provider will get called once and only once before the tests. You do not need a static data fixture on the class because phpunit handles the data fixture for you when you use data providers.
I have the following test case:
include_once('../Logger.php');
class LoggerTest extends PHPUnit_Framework_TestCase {
public function providerLogger() {
return new Logger;
}
/**
* #dataProvider providerLogger
*/
public function testAddStream($logger) {
$this->assertTrue(false);
}
}
When I run it in PHPUnit, I get:
PHPUnit 3.4.14 by Sebastian Bergmann.
..........
Time: 0 seconds, Memory: 5.75Mb
OK (1 tests, 0 assertions)
Test should fail, but it doesn't. I tried having:
public function providerLogger() {
return array(new Logger);
}
But I get:
The data provider specified for LoggerTest::testAddStream is invalid.
I tried declaring it static (like the manual says), but still no difference.
I remember having it working in a similar fashion before, but I could be wrong. What am I missing?
Thanks in advance for your help.
PHPUnit 3.4.14 (taken from PEAR) on PHP 5.3.3
Minor update: It's OK to use instance methods as provider since version 3.2 (or somewhere around that). Have a look at the comments
The provider must look like this.
public static function providerLogger() {
return array(
array(new Logger)
);
}
First of all: The method must be static if you are using phpunit version lower than 3.3 .
The array s are important. Its not that hard to understand. The outer array has one value for each iteration the test should get called. Here the test just get called once. The inner arrays are the parameters (in order) the test is invoked with. Your test expects exactly one parameter, so the inner arrays always needs exactly one value. Another little example
public static function addTestProvider () {
return array(
/* First + Second = third? */
array(1,4,5),
array(3,3,6),
array(5,5,6)
);
}
public function testAdd ($a, $b, $result) {
$this->assertEquals($result, $a + $b);
}
Here testAdd gets executed 3 times, one for every second-level array, and it will receive the values from the inner array s. You may notice, that the test will fail and provides you a message in which iteration of the dataset (here #3, because 5+5 is not 6 ;)) the assertion failed.
I had the same problem, and it was solved, when i deleted the empty constructor,that was auto generated. Iam not sure why this solves the problem. I also had no test method named like the class. The provider method doesnt need to be static, so far my test run without static. But also run when i make the provider method static
<?php
require_once 'calculator.php';
/**
* Calculator test case.
*/
class CalculatorTest extends PHPUnit_Framework_TestCase {
/**
* #var Calculator
*/
private $Calculator;
/**
* Prepares the environment before running a test.
*/
protected function setUp() {
parent::setUp ();
// TODO Auto-generated CalculatorTest::setUp()
$this->Calculator = new Calculator(/* parameters */);
}
/**
* Cleans up the environment after running a test.
*/
protected function tearDown() {
// TODO Auto-generated CalculatorTest::tearDown()
$this->Calculator = null;
parent::tearDown ();
}
/**
* Constructs the test case.
*/
public function __construct() {
// TODO Auto-generated constructor
}
/**
* Tests Calculator->add()
*
* #dataProvider provider
*/
public function testAdd($a, $b, $c) {
// TODO Auto-generated CalculatorTest->testAdd()
//$this->markTestIncomplete ( "add test not implemented" );
//$this->Calculator->add(/* parameters */);
$this->assertEquals($this->Calculator->add($a, $b), $c);
}
public static function provider()
{
return array(
array(1, 1, 1),
array(1, 1, -1),
array(4, 2, 2),
array(1, 1, 1)
);
}
}
is the complete set of code
I have also found that you cannot directly chain data providers:
class ProviderTest extends PHPUnit_Framework_TestCase {
public function provider() {
return array(array('test'));
}
/**
* #dataProvider provider
*/
public function providerTest1($test) {
$this->assertTrue($test);
return array(array($test));
}
/**
* #dataProvider providerTest1
*/
public function providerTest2($test) {
$this->assertEquals('test', $test);
}
}
Apparently, PHPUnit calls all the provider functions before running any tests, so you can't even use separate provider functions to feed test result data to other tests. The best you can do is to simulate:
class ProviderTest extends PHPUnit_Framework_TestCase {
private $provider_data = array();
public function provider() {
return array(array('test'));
}
/**
* #dataProvider provider
*/
public function testProvider1($test) {
$this->assertFalse(empty($test));
array_push($this->provider_data, array($test));
}
/**
* #depends testProvider1
*/
public function testProvider2($test = NULL) {
if(is_null($test)) {
// simulate a provider
foreach($this->provider_data as $row) {
call_user_func_array(array($this, __METHOD__), $row);
}
} else {
$this->assertEquals('test', $test);
}
}
}
Remove the parameter from public function testAddStream($logger) and try again. I don't believe PHPUnit will invoke a test which requires parameters it is unable to pass.
Behold I have achieved a pattern to achieve test dependencies for dataProviders! In this way you can chain dataProviders.
class ProviderDependencyTest extends PHPUnit_Framework_TestCase
{
private static $dataSet;
public function provideData()
{
self::$dataSet = array(
array(2,2,4),
array(1,0,2),
array(0,0,0)
);
//use static storage so you don't have to call the dataProvider again
return self::$dataSet;
}
public function testProvideAdd()
{
$data = self::$dataSet;
$this->assertEquals(3,count($data[0]));
return $data[0];
}
/**
* #depends testProvideAdd
*/
public function testAdd($data)
{
$sum = $data[0] + $data[1];
$this->assertEquals($data[2], $sum);
return array($sum,$data[0],$data[1]);
}
/**
* #depends testAdd
*/
public function testSubtract($data)
{
$difference = $data[0] - $data[1];
$this->assertEquals($data[2], $difference);
return array($difference,$data[0],$data[1]);
}
/**
* #depends testSubtract
*/
public function testMultiply($data)
{
$product = $data[0] * $data[2];
$this->assertEquals($data[1], $product);
return $product;
}
/**
* #depends testMultiply
*
* #dataProvider provideData
*/
public function testMath($a,$b,$c)
{
//don't redo the first set of tests
if(array($a,$b,$c) == self::$dataSet[0])
{
return;
}
$sum = $this->testAdd(array($a,$b,$c));
$difference= $this->testSubtract($sum);
$product = $this->testMultiply($difference);
$this->assertInternalType('integer', $product);
}
}
The 2nd data set fails 1 test to illustrate.