Reputation: 1405
I am writing a Product Class, the job of the Class is to take in a product id
and to output the corresponding product name
.
For e.g.:
$Product = new Product;
$Product->id = "ff62";
$Product->readId();
echo $Product->name;
// returns a string with at least 5 characters.
My PHPUnit test method looks like:
$Product = new Product;
$Product->id = "ff62"; // needs to be a variable
$Product->readId();
$this->assertEquals(gettype($Product->name), 'string');
However, my aim is to check for a different product ID each time instead of ff62
which may or may not exist in database.
Ideally one should be able to define the id
variable during testing.
What is the best way to test for dynamic variables as such?
Upvotes: 1
Views: 1591
Reputation: 4826
Faker is one way to do it, but I would hesitate to say it is the "best way."
Your requirements are: 1. Test a set of different variables. 2. Those variables may or may not exist in the database.
But you have several problems with how you have designed this test:
gettype()
and comparing it to string. This is a bad idea. If product 54 is "foo", and your test is returning "bar" for 54, it will pass. This is Programming by Coincidence. I.e., it works, but not on purpose.The proper way to structure this test is using @dataProvider and database fixtures / testing.
Here's what that would look like:
<?php
namespace Foo\Bar;
use PHPUnit\DbUnit\TestCaseTrait;
use PHPUnit\Framework\TestCase;
use \PDO;
USE \Exception;
class ProductTest extends TestCase
{
use TestCaseTrait;
// only instantiate pdo once for test clean-up/fixture load
static private $pdo = null;
// only instantiate PHPUnit_Extensions_Database_DB_IDatabaseConnection once per test
private $conn = null;
final public function getConnection()
{
if ($this->conn === null) {
if (self::$pdo == null) {
self::$pdo = new PDO($GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD']);
}
$this->conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAME']);
}
return $this->conn;
}
public function getDataSet()
{
return $this->createMySQLXMLDataSet('tests/unit/testdata/sampleproductdata.xml');
}
/**
* Tests products against known good data in the database fixture.
* @param $id
* @param $expectedName
* @dataProvider providerTestProduct
*/
public function testProduct($id, $expectedName) {
$Product = new Product;
$Product->id = $id;
$Product->readId();
$this->assertSame($expectedName, $Product->name);
}
/**
* Provides data that should appear in the database.
* @return array
*/
public function providerTestProduct() {
// id , expectedName
return [ [ "ff62" , "fooproduct"]
, [ "dd83" , "barproduct"]
, [ "ls98" , "bazproduct"]
];
}
/**
* Tests products against known-bad data to ensure proper exceptions are thrown.
* @param $id
* @param $expectedName
*/
public function testProductExceptions($id, $expectedName) {
$Product = new Product;
$Product->id = $id;
$this->expectException(Exception::class);
$Product->readId();
}
/**
* Provides test data that when queried against the database should produce an error.
* @return array
*/
public function providerTestProductExceptions() {
// id , expectedName
return [ [ "badtype" , "fooproduct"] //Wrong id type
, [ "aaaa" , "barproduct"] //Does not exist
, [ null , "bazproduct"] //null is a no-no.
];
}
}
Here's a breakdown:
use
to declare what classes you're using in the test. TestCaseTrait
to properly setup your TestCase$pdo
variable will hold your database connection for your class / test. getConnection()
is required. This will use the database, username, and password you have configured in your phpunit.xml
file. ReferencegetDataSet()
goes and reads your datasource (fixture), then, truncates your database on your workstation / dev box, imports all the data from the fixture to put the database in a known state. (Be sure to backup your data before you do this. It's lossy on purpose. Never execute on production).Next, you have two pairs of methods for the test cases: a test and a data provider.
The data provider in each case provides an ID you want to test, and the expected result. In the case of testProduct
and providerTestProduct
, we are providing ID that should exist in the database (as ensured by the fixture above). We can then check that Product::readId()
is not only returning a string, but is actually returning the correct string.
In the second case, testProductException()
and providerTestProductException()
, we are intentionally sending bad values to the class to trigger exceptions, and then checking to make sure those bad values actually produces the desired behavior: failure / thrown exceptions.
Upvotes: 2
Reputation: 1405
I found out the best way to do this is to use Faker.
https://github.com/fzaninotto/Faker
While I was trying to test against different instances of a Product, I could definitely use Faker to randomly generate a product and test if the Product was being retrieved properly from the database.
Although majorly used in Laravel, Symfony, etc. It's quite easy to use even in custom PHP frameworks.
Upvotes: 0
Reputation: 373
You can randomise your dataset using random number generation.
$value = dechex(random_int(0, 255)).dechex(random_int(0, 255));
$Product = new Product;
$Product->id = $value;
$Product->readId();
$this->assertEquals('string', gettype($Product->name));
$this->assertEquals($value, $Product->name);
One usually puts the expected value to the left, and the actual one to the right.
Upvotes: 0