My problem:
How my I test some MVC part with Pest in CakePHP, for example:
When I´m use PHPUnit, I´ll type:
public class ItemTest extends TestCase
And it will show what thing that I wanna test, but how do that with Pest?
(Obs.: I´m really new on tests, so I accept anything that will make me improve :) )
After you installed Pest you just have to follow its syntax and documentation, removing all the OOP code and just using test() or it(). The file name will be the same. This is an example from CakePHP docs:
class ArticlesTableTest extends TestCase
{
public $fixtures = ['app.Articles'];
public function setUp()
{
parent::setUp();
$this->Articles = TableRegistry::getTableLocator()->get('Articles');
}
public function testFindPublished()
{
$query = $this->Articles->find('published');
$this->assertInstanceOf('Cake\ORM\Query', $query);
$result = $query->enableHydration(false)->toArray();
$expected = [
['id' => 1, 'title' => 'First Article'],
['id' => 2, 'title' => 'Second Article'],
['id' => 3, 'title' => 'Third Article']
];
$this->assertEquals($expected, $result);
}
}
With Pest, it will become:
beforeEach(function () {
$this->Articles = TableRegistry::getTableLocator()->get('Articles');
});
test('findPublished', function () {
$query = $this->Articles->find('published');
expect($query)->toBeInstanceOf('Cake\ORM\Query');
$result = $query->enableHydration(false)->toArray();
$expected = [
['id' => 1, 'title' => 'First Article'],
['id' => 2, 'title' => 'Second Article'],
['id' => 3, 'title' => 'Third Article']
];
expect($result)->toBe($expected);
});
Note that toBeInstanceOf() and toBe() are part of the available Pest expectations.
Finally, instead of running your test suite with $ ./vendor/bin/phpunit, you will run it with $ ./vendor/bin/pest.
Related
I'm just wondering how you'd mock a service in Symfony.
This is what I've ended up with so far and it's not working as expected. I expect the getVenuesData() method from the NetballFeedService to dump the mocked data in the test (I've set a DD in the command). But when I run the test, it dumps the real time data being pulled from the NetballFeedService.
Test:
public function it_imports_venues(): void
{
$kernel = self::bootKernel();
$netballAPIMock = $this->createMock(NetballFeedService::class);
$netballAPIMock->method('getVenuesData')->willReturn([
800 => ['name' => 'Venue 1', 'location' => 'MEL'],
801 => ['name' => 'Venue 2', 'location' => 'ADE']
]);
$this->getContainer()->set('netballAPI', $netballAPIMock);
$container = $kernel->getContainer();
$container->set('netballAPI', $netballAPIMock);
$application = new Application($kernel);
$command = $application->find('app:feed:import-venues');
$commandTester = new CommandTester($command);
$commandTester->execute([]);
$commandTester->assertCommandIsSuccessful();
}
Command being tested:
protected static $defaultName = 'app:feed:import-venues';
public function __construct(
private readonly NetballFeedService $netballFeedService,
private readonly VenueService $venueService
)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$data = $this->netballFeedService->getVenuesData();
dd($data);
$this->venueService->updateVenuesDataFromFeed($data);
return 0;
}
Class method expected to return mocked data:
public function getVenuesData(): array
{
$response = $this->client->request('GET', self::FIXTURES_URL);
$rawData = json_decode($response->getBody()->getContents(), true);
$rounds = $rawData['data']['results'];
$parsedVenuesData = [];
foreach ($rounds as $round) {
foreach ($round['matches'] as $matches) {
$venueId = $matches['venueId'];
if (array_key_exists($venueId, $matches)) {
continue;
}
$parsedVenuesData[$matches['venueId']] = [
'name' => $matches['venueName'],
'location' => $matches['venueLocation']
];
}
}
ksort($parsedVenuesData);
return $parsedVenuesData;
}
I'm trying to replicate my Laravel test code in Symfony:
/** #test */
public function it_imports_venues()
{
$this->withExceptionHandling();
$this->instance(
NetballAPI::class,
Mockery::mock(NetballAPI::class, function (MockInterface $mock) {
$mock->shouldReceive('getVenuesData')->once()
->andReturn([
800 => [
'name' => 'Venue 1',
'location' => 'MEL'
],
801 => [
'name' => 'Venue 2',
'location' => 'ADE'
],
]);
})
);
$this->artisan('import:venues');
$this->assertDatabaseHas('venues', [
'id' => 800,
'name' => 'Venue 1',
'location' => 'MEL'
]);
$this->assertDatabaseHas('venues', [
'id' => 801,
'name' => 'Venue 2',
'location' => 'ADE'
]);
services_test.yml:
services:
_defaults:
public: true
App\Service\NetballFeedService:
public: true
alias: netballAPI
Just wondering how I can mock the NetballFeedService class in the test instance? Thanks in advance.
I was configuring the service_test.yaml wrong. It should be:
services:
App\Service\NetballFeedService:
public: true
app.netballFeedService:
alias: App\Service\NetballFeedService
public: true
Also, I'm not really used to using YAML files for configs. It's not very readable especially compared to it's PHP file counterpart in this instance. So I'll be experimenting on using both YAML & PHP files for configs/routes etc.
I am testing an eager loading relationship which contains many to many relations. Right now I have the queries and attachments within the test. I'm wondering if there is a way to move them into the factory, rather than including it as part of your test. This would limit the size of the test and then these relations could be created and used every time a film factory is created.
test
public function grabFilmTest()
{
$film = factory(Film::class)->create();
$categories = Category::where('main-cat', 'Science')->where('sub-cat', 'Fiction')->first();
$languages = Languages::where('name', 'english')->first();
$film->categories()->attach($categories->id);
$film->languages()->attach($languages->id);
$response = $this->json('GET', '/film/' . $film->id)
->assertStatus(200);
$response
->assertExactJson([
'id' => $film->id,
'name' => $film->name,
'description' => $film->description,
'categories' => $film->categories->toArray(),
'languages' => $film->languages->toArray()
}
filmFactory
$factory->define(\App\Models\Film::class, function (Faker $faker){
return [
'id' => $faker->uuid,
'name' => $faker->text,
'description' => $faker->paragraph,
];
});
If anyone could help with how i could do this or an example it would be great :D
You could use factory states and factory callbacks.
$factory->define(\App\Models\Film::class, function (Faker $faker){
return [
'id' => $faker->uuid,
'name' => $faker->text,
'description' => $faker->paragraph,
];
});
$factory->define(\App\Models\Category::class, function (Faker $faker){
return [
// Category fields
];
});
$factory->define(\App\Models\Language::class, function (Faker $faker){
return [
// Language fields
];
});
$factory->afterCreatingState(\App\Models\Film::class, 'with-category', function (\App\Models\Film $film) {
$category = factory(\App\Models\Category::class)->create();
$film->categories()->attach($category->id);
});
$factory->afterCreatingState(\App\Models\Film::class, 'with-language', function (\App\Models\Film $film) {
$language = factory(\App\Models\Language::class)->create();
$film->categories()->attach($language->id);
});
Then you can use in tests like this:
public function grabFilmTest()
{
$film = factory(Film::class)->create();
$filmWithCategory = factory(Film::class)->state('with-category')->create();
$filmWithLanguage = factory(Film::class)->state('with-language')->create();
$filmWithCategoryAnLanguage = factory(Film::class)->states(['with-category', 'with-language'])->create();
// ...
}
PS: I don't recommend using existing data. From experience, I can tell you that can become really painful.
You can use factory callbacks to do it in the factory file:
<?php
use \App\Models\Film;
use \App\Models\Category;
use \App\Models\Languages;
$factory->define(Film::class, function(Faker $faker){
return [
'id' => $faker->uuid,
'name' => $faker->text,
'description' => $faker->paragraph,
];
});
$factory->afterCreating(Film::class, function(Film $film, Faker $faker) {
$category = Category::where('main-cat', 'Science')->where('sub-cat', 'Fiction')->first();
$language = Languages::where('name', 'english')->first();
$film->categories()->attach($category);
$film->languages()->attach($language);
});
using factory is a cleaner way to create seed data.
I can generate the result from another method of foreach or for loop. but how to do it with the factory ?
below is the post factory page
<?php
/** #var \Illuminate\Database\Eloquent\Factory $factory */
use App\Post;
use App\MetaData;
use Faker\Factory;
$factory->define(Post::class, function () {
$faker = Faker\Factory::create('en_IN');
$w = $faker->unique()->sentence.' '.mt_rand(0,1000);
$r = [
'title' => $w,
'slug' => strtolower(str_replace(' ', '-', $w)),
'banner' => 'https://source.unsplash.com/random/600x600',
'content' => $faker->text,
'views' => mt_rand(0,1000),
'status' => rand(0,1),
'creator_id' => mt_rand(0,100),
'moderator_id' => mt_rand(0,100),
];
$factory->define(MetaData::class, function () {
return [
'for' => 'article',
'record_id' => $r->id,
'title' => $w,
'slug' => strtolower(str_replace(' ', '-', $w)),
'description' => $faker->sentences,
'banner' => 'https://source.unsplash.com/random/600x600',
'keywords' => $faker->words,
'status' => 1,
'creator_id' => mt_rand(0,100),
'moderator_id' => mt_rand(0,100),
];
});
return $r;
});
I want to do something like this, but end up with an error like below:
ErrorException : Undefined variable: factory
at /Users/dragonar/Dev/pdp/database/factories/PostFactory.php:23
19| 'creator_id' => mt_rand(0,100),
20| 'moderator_id' => mt_rand(0,100),
21| ];
22|
> 23| $factory->define(MetaData::class, function () {
24| return [
25| 'for' => 'article',
26| 'record_id' => $r->id,
27| 'title' => $w,
Exception trace:
1 Illuminate\Foundation\Bootstrap\HandleExceptions::handleError("Undefined variable: factory", "/Users/dragonar/Dev/pdp/database/factories/PostFactory.php", [Object(Faker\Generator), "Aut voluptatum sed aut beatae. 380"])
/Users/dragonar/Dev/pdp/database/factories/PostFactory.php:23
2 Illuminate\Database\Eloquent\Factory::{closure}(Object(Faker\Generator), [])
/Users/dragonar/Dev/pdp/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:273
Please use the argument -v to see more details.
if this solves, I also want to add categories and tags to the same post.
please try this
$factory->define('App\MetaData', function($faker) use ($factory) {
// Your stuff here
});
The problem here is that you are trying to define the child inside the factory of the parent.
You are getting an ErrorException : Undefined variable: factory error, that tells you that $factory is not defined. This is because you are doing it in the closure for the post factory.
One way you can get around this, is to reference the post when creating the meta data, so you do not create them both at the same time.
<?php
/** #var \Illuminate\Database\Eloquent\Factory $factory */
use App\Post;
use App\MetaData;
use Faker\Factory;
$factory->define(Post::class, function () {
$faker = Faker\Factory::create('en_IN');
$w = $faker->unique()->sentence.' '.mt_rand(0,1000);
return [
'title' => $w,
'slug' => strtolower(str_replace(' ', '-', $w)),
'banner' => 'https://source.unsplash.com/random/600x600',
'content' => $faker->text,
'views' => mt_rand(0,1000),
'status' => rand(0,1),
'creator_id' => mt_rand(0,100),
'moderator_id' => mt_rand(0,100),
];
});
$factory->define(MetaData::class, function () {
return [
'for' => 'article',
'record_id' => factory(Post::class)->id,
'title' => $w,
'slug' => strtolower(str_replace(' ', '-', $w)),
'description' => $faker->sentences,
'banner' => 'https://source.unsplash.com/random/600x600',
'keywords' => $faker->words,
'status' => 1,
'creator_id' => mt_rand(0,100),
'moderator_id' => mt_rand(0,100),
];
});
When doing it like this, you first create the post, then you create the metadata and reference the post when creating it. However, I am not sure that helps you with your issue. So what you could do is create a helper class, where you can pass in some overrides for both classes, in case you want to control what data goes into each model.
Can be done like so:
<?php
use App\Post;
use App\MetaData;
class FactoryHelper {
public static function createPostWithMetaData(array $postAttributes = [], array $metaDataAttributes = [])
{
$post = factory(Post::class)->create(postAttributes);
$metaData = factory(MetaData::class)->create(array_merge([
'record_id' => $post->id
], $metaDataAttributes));
return $post;
}
}
I updated Doctrine 2.5 to 2.6 in my project and phpspec is broken.
The function getEntityChangeSet() is now returned by reference. It seems not to be supported by phpspec.
$unitOfWork
->getEntityChangeSet($site)
->willReturn(['_dataParent' => [0 => 2, 1 => 3]]);
The response is
returning by reference not supported
the underlying function (doctrine/doctrine2) is
public function & getEntityChangeSet($entity)
{
$oid = spl_object_hash($entity);
$data = [];
if (!isset($this->entityChangeSets[$oid])) {
return $data;
}
return $this->entityChangeSets[$oid];
}
Do you know if it's possible to bypass this or change test for make it work?
The answer has been given on Twitter by #Pamilme
You have to mock UnitOfWork with Mockery. An example can be found here:
/** #var UnitOfWork|MockInterface $unitOfWork */
$unitOfWork = Mockery::mock(UnitOfWork::class);
$unitOfWork->shouldReceive('getEntityChangeSet')->withArgs([$productAttribute->getWrappedObject()])->andReturn([
'configuration' => [
['choices' => [
'8ec40814-adef-4194-af91-5559b5f19236' => 'Banana',
'1739bc61-9e42-4c80-8b9a-f97f0579cccb' => 'Pineapple',
]],
['choices' => [
'8ec40814-adef-4194-af91-5559b5f19236' => 'Banana',
]],
],
]);
$entityManager->getUnitOfWork()->willReturn($unitOfWork);
if you extends TestCase on your test class, you can also do something like:
$uow = $this->createMock(UnitOfWork::class);
$uow->method('getEntityChangeSet')->willReturn(['_dataParent' => [0 => 2, 1 => 3]);
I seem to be having a problem with fixtures in Yii. The problem seems to be the following,
public $fixtures=array('projects'=>'Project');
The model Project exists and I have the fixtures in a file name tbl_project.php in the fixtures folder of tests and my table name is called tbl_project. Inside the fixtures file is the following.
return array(
'project1' => array(
'name' => 'Test Project 1',
'description' => 'This is test project 1',
'create_time' => '',
'create_user_id' => '',
'update_time' => '',
'update_user_id' => '',
),
'project2' => array(
'name' => 'Test Project 2',
'description' => 'This is test project 2',
'create_time' => '',
'create_user_id' => '',
'update_time' => '',
'update_user_id' => '',
),
'project3' => array(
'name' => 'Test Project 3',
'description' => 'This is test project 3',
'create_time' => '',
'create_user_id' => '',
'update_time' => '',
'update_user_id' => '',
),
);
This is actually from the book "Agile Web Application Development with Yii". When I run the test case I get the following with no test result information.
PHPUnit 3.6.10 by Sebastian Bergmann.
Configuration read from ETC/protected/tests/phpunit.xml
If I remove the fixtures array from the top I get the following.
Time: 0 seconds, Memory: 9.25Mb
There was 1 error:
1) ProjectTest::testRead
Exception: Unknown method 'projects' for class 'ProjectTest'.
Which obviously makes sense. I dont know what Im doing wrong.
Make sure the test class looks like this
class ProjectTest extends CDbTestCase{
protected $fixtures = array(
'projects' => 'Project',
);
public function testRead(){
$receivedProject = $this->projects('Project1');
$this->assertTrue($receivedProject instanceof Project);
$this->assertEquals($receivedProject->name,'test 1');
}
...`
Check the fixture configuration in protected/config/test.php ... Should look like ...
...
'components'=>array(
'fixture'=>array(
'class'=>'system.test.CDbFixtureManager',
),
'db'=>array(
'connectionString' =>'mysql:host=localhost;dbname=your_db_name',
'emulatePrepare' => true,
'username' => 'username',
'password' => 'passwd',
'charset' => 'utf8',
),
....
Ultimately check the permissions on the fixture file making sure it can be read
Also be sure you are calling parents setUp() method in your own setUp()
class SomeTest extends CDbTestCase {
public $fixtures = array(
'somes' => 'Some',
);
protected function setUp() {
parent::setUp();
// your code....
}
// your tests .......................
}
Is your test class deriven from CDbTestCase instead of CTestCase?
your Test Class should look something like this:
class ProjectTest extends CDbTestCase{
protected $fixtures = array(
'projects' => 'Project',
);
public function testRead(){
$receivedProject = $this->projects('Project1');
$this->assertTrue($receivedProject instanceof Project);
$this->assertEquals($receivedProject->name,'test 1');
}
class ProjectTest extends CDbTestCase
{
public function testCreate()
{
//CREATE a new Project
$newProject=new Project;
$newProjectName = 'Test Project Creation';
$newProject->setAttributes(array(
'name' => $newProjectName,
'description' => 'This is a test for new project creation',
'createTime' => '2009-09-09 00:00:00',
'createUser' => '1',
'updateTime' => '2009-09-09 00:00:00',
'updateUser' => '1',
)
);
$this->assertTrue($newProject->save(false));
//READ back the newly created Project to ensure the creation worked
$retrievedProject=Project::model()->findByPk($newProject->id);
$this->assertTrue($retrievedProject instanceof Project);
$this->assertEquals($newProjectName,$retrievedProject->name);
}
public function testRead()
{
$retrievedProject = $this->projects('project1');
$this->assertTrue($retrievedProject instanceof Project);
$this->assertEquals('Test Project 1',$retrievedProject->name);
}
public function testUpdate()
{
$project = $this->projects('project2');
$updatedProjectName = 'Updated Test Project 2';
$project->name = $updatedProjectName;
$this->assertTrue($project->save(false));
//read back the record again to ensure the update worked
$updatedProject=Project::model()->findByPk($project->id);
$this->assertTrue($updatedProject instanceof Project);
$this->assertEquals($updatedProjectName,$updatedProject->name);
}
public function testDelete()
{
$project = $this->projects('project2');
$savedProjectId = $project->id;
$this->assertTrue($project->delete());
$deletedProject=Project::model()->findByPk($savedProjectId);
$this->assertEquals(NULL,$deletedProject);
}
public function testGetUserOptions()
{
$project = $this->projects('project1');
$options = $project->userOptions;
$this->assertTrue(is_array($options));
$this->assertTrue(count($options) > 0);
}
public $fixtures=array(
'projects'=>'Project',
'users'=>'User',
'projUsrAssign'=>':tbl_project_user_assignment',
);
}
make sure that setup come with its parent
<i>public function setUp() {
parent::setUp();
}</i>