Unit Testing with Model::Find Fails in Laravel - laravel

I am creating unit tests for my rather large Laravel application.
This test passes all assertions. It creates a new database entry for the advertising status model.
/** #test **/
public function create_a_new_advertising_status_test()
{
$status = AdvertisingStatus::create(['name' => 'Test']);
$this->assertNotNull($status);
$this->assertCount(1, AdvertisingStatus::all());
$this->assertEquals($status->name, $this->advertisingStatus['name']);
}
This test fails the NotNull assertion as it says $status is null, which means that the Find method is failing.
/** #test **/
public function edit_an_existing_advertising_status_entry()
{
$status = AdvertisingStatus::create(['name' => 'Test']);
// $status = AdvertisingStatus::first(); // This successfully finds the database entry
$status = AdvertisingStatus::find(1); // This fails to find the database entry
$status->name = 'Edit';
$status->save();
$this->assertNotNull($status);
$this->assertCount(1, AdvertisingStatus::all());
$this->assertEquals($status->name, "Edit");
}
It appears like the find function and subsequently the where function takes too long to locate the entry, so the test renders the $status variable null.
Aside from using the Model::first() function, does anyone have any idea how to overcome this?
I'm wondering if it's because my application is extremely large and takes a long time to run because I'm using RefreshDatabase

You have to create a factory first, then you will use it like this:
$status = factory(AdvertisingStatus::class)->create(['name' => 'Test']);

Related

Testing Laravel View Composers with Mockery

I am trying to test my View Composers. Whenever I pass an object to the $view->with('string', $object), my test fails. This is when I do the test like this:
$view
->shouldReceive('with')
->with('favorites', $this->user->favorites(Ad::class)->get())
->once();
I'm pretty sure this is due to strict checking. So I looked around and saw this issue. However, I can't seem to get it working. The closure return true, but the test fails:
Mockery\Exception\InvalidCountException : Method with('favorites',
< Closure===true >) from Mockery_3_Illuminate_View_View should be called
exactly 1 times but called 0 times.
Here is my current test
public function it_passes_favorites_to_the_view()
{
$this->setUpUser(); // basically sets $this->user to a User object
Auth::shouldReceive('user')
->once()
->andReturn($this->user);
$composer = new FavoritesComposer();
$view = Mockery::spy(View::class);
$view
->shouldReceive('with')
->with('favorites', Mockery::on(function($arg) {
$this->assertEquals($this->user->favorites(Ad::class)->get(), $arg);
}))
->once();
$composer->compose($view);
}
FavoritesComposer class:
public function compose(View $view)
{
$user = Auth::user();
$favorites = $user
? $user->favorites(Ad::class)->get()
: collect([]);
$view->with('favorites', $favorites);
}
How do I test object like this?
I fixed the issue by replacing $view->with('favorites', $favorites); with $view->with(['favorites' => $favorites]); and then testing it like this:
$view
->shouldReceive('with')
->with(['favorites' => $this->user->favorites(Ad::class)->get()])
->once();
So, essentially using only one parameter in the with()-method is what fixed it for me.

Laravel Testing - Should I be creating dependant resources in each test?

Starting on a new build with Laravel Spark 6 (Laravel 5.6) and decided to give TDD a try.
First test was lovely, I created a unit test to make sure that users can create an team.
(Pseudo code):
class AddNewTeamTest extends TestCase
{
/** #test */
public function admin_can_create_new_team()
{
// Create a user account
$data = [
// Information for tea,
];
$response = $this->withHeaders([
'X-Requested-With' => 'XMLHttpRequest',
])
->actingAs($user)
->json('POST', '/api/teams', $data);
$response
->assertStatus(201);
}
}
Using this in TDD style was a nice process, but now I want to be able to write a test for adding a member to that team.
It seems backwards that in this new test, I would run all of the code in my first test. Is there anyway around this? For the new test I would need a user and team already created before I could test adding a user to that team..
Any links or advice welcome!
You can use function setUp() and build your enviroment inside it.
So your class should looks like that:
class AddNewTeamTest extends TestCase
{
protected function setUp()
{
// Create a user account
// Create your enviroment, etc.
$this->actingAs($user)
}
/** #test */
public function admin_can_create_new_team()
{
$data = [
// Information for tea,
];
$response = $this->withHeaders([
'X-Requested-With' => 'XMLHttpRequest',
])
->json('POST', '/api/teams', $data);
$response
->assertStatus(201);
}
public function testAnother()
{
\\your next test
}
}
If you need a team in next few cases, that should be added in setUp().
Also, you can make your next test needed ypur previous one. In that case you can return something in admin_can_create_new_team() and take as parameter in testAnother()
More info:
https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.test-dependencies

PHPUnit - post to an existing controller does not return an error

I am new to PHPUnit and TDD. I just upgrade my project from Laravel 5.4 to 5.5 with phpunit 6.5.5 installed . In the learning process, I wrote this test:
/** #test */
public function it_assigns_an_employee_to_a_group() {
$group = factory(Group::class)->create();
$employee = factory(Employee::class)->create();
$this->post(route('employee.manage.group', $employee), [
'groups' => [$group->id]
]);
$this->assertEquals(1, $employee->groups);
}
And I have a defined route in the web.php file that look like this
Route::post('{employee}/manage/groups', 'ManageEmployeeController#group')
->name('employee.manage.group');
I have not yet created the ManageEmployeeController and when I run the test, instead of get an error telling me that the Controller does not exist, I get this error
Failed asserting that null matches expected 1.
How can I solve this issue please?
The exception was automatically handle by Laravel, so I disabled it using
$this->withoutExceptionHandling();
The test method now look like this:
/** #test */
public function it_assigns_an_employee_to_a_group() {
//Disable exception handling
$this->withoutExceptionHandling();
$group = factory(Group::class)->create();
$employee = factory(Employee::class)->create();
$this->post(route('employee.manage.group', $employee), [
'groups' => [$group->id]
]);
$this->assertEquals(1, $employee->groups);
}
You may not have create the method in the Controller but that doesn t mean your test will stop.
The test runs.It makes a call to your endpoint. It returns 404 status because no method in controller found.
And then you make an assertion which will fail since your post request
wasn't successful and no groups were created for your employee.
Just add a status assertion $response->assertStatus(code) or
$response->assetSuccessful()

Testing Laravel (5.1) console commands with phpunit

What is the best way to test Laravel console commands?
Here is an example of a command I'm running. It takes in a value in the constructor and in the handle method.
class DoSomething extends Command
{
protected $signature = 'app:do-something';
protected $description = 'Does something';
public function __construct(A $a)
{
...
}
public function handle(B $b)
{
...
}
}
In my test class, I can mock both A and B, but I can't figure out how to pass $a in.
$this->artisan('app:do-something', [$b]);
Is it possible? Or am I going about this all wrong? Should I pass everything in thought the handle() method?
Thanks.
You will have to change around how you call the command in testing, but it is possible to mock an object passed through.
If the class used by Artisan is dependency-injected like this:
public function __construct(ActualObject $mocked_A)
{
//
}
Then write up the test case like this:
$mocked_A = Mockery::mock('ActualObject');
$this->app->instance('ActualObject', $mocked_A);
$kernel = $this->app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArrayInput([
'command' => 'app:do-something',
]),
$output = new Symfony\Component\Console\Output\BufferedOutput
);
$console_output = $output->fetch();
The $this->app->instance('ActualObject', $mocked_A); line is where you are able to call upon and use the mocked version of your class, or object, instead of the actual.
This will work in Laravel or Lumen.

What is the difference between trait and behavior in cakephp 3?

I find soft delete in cakephp 3 that implemented via traits. And I try to implement it via behaviors. But unlike the trait version, SoftDeleteBehavior do not work.
I have this line in my model initialize method:
$this->addBehavior('SoftDelete');
And this is my SoftDeleteBehavior
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\ORM\RulesChecker;
use Cake\Datasource\EntityInterface;
use App\Model\Behavior\MyQuery;
class SoftDeleteBehavior extends Behavior {
public $user_id = 1;
public function getDeleteDate() {
return isset($this->deleteDate) ? $this->deleteDate : 'deleted';
}
public function getDeleter() {
return isset($this->deleter) ? $this->deleter : 'deleter_id';
}
public function query() {
return new MyQuery($this->connection(), $this);
}
/**
* Perform the delete operation.
*
* Will soft delete the entity provided. Will remove rows from any
* dependent associations, and clear out join tables for BelongsToMany associations.
*
* #param \Cake\DataSource\EntityInterface $entity The entity to soft delete.
* #param \ArrayObject $options The options for the delete.
* #throws \InvalidArgumentException if there are no primary key values of the
* passed entity
* #return bool success
*/
protected function _processDelete($entity, $options) {
if ($entity->isNew()) {
return false;
}
$primaryKey = (array)$this->primaryKey();
if (!$entity->has($primaryKey)) {
$msg = 'Deleting requires all primary key values.';
throw new \InvalidArgumentException($msg);
}
if (isset($options['checkRules']) && !$this->checkRules($entity, RulesChecker::DELETE, $options)) {
return false;
}
$event = $this->dispatchEvent('Model.beforeDelete', [
'entity' => $entity,
'options' => $options
]);
if ($event->isStopped()) {
return $event->result;
}
$this->_associations->cascadeDelete(
$entity,
['_primary' => false] + $options->getArrayCopy()
);
$query = $this->query();
$conditions = (array)$entity->extract($primaryKey);
$statement = $query->update()
->set([$this->getDeleteDate() => date('Y-m-d H:i:s') , $this->getDeleter() => $this->user_id])
->where($conditions)
->execute();
$success = $statement->rowCount() > 0;
if (!$success) {
return $success;
}
$this->dispatchEvent('Model.afterDelete', [
'entity' => $entity,
'options' => $options
]);
return $success;
}
If I use trait, SoftDeleteTrait works in true manner. But SoftDeleteBehavior do not work properly!
One is a PHP language construct, the other is a programmatic concept. You may want to read upon what traits are, so that you understand that this question, as it stands, doesn't make too much sense. Also stuff like "doesn't work" doesn't serve as a proper problem description, please be more specific in the future.
That being said, CakePHP behaviors do serve the purpose of horizontal code reuse, similar to traits, as opposed to vertical reuse by inheritance.
However, even if they have conceptual similarities, you cannot simply exchange them as you seem to do in your code, a trait will be composited into the class on which it is used, so that it becomes part of it as if it were written directly in the class definition, and therefore has the ability to overwrite inherited code like the Table::_processDelete() method, a behavior on the other hand is a totally independent class, which is being instantiated and injected as a dependency into a table class at runtime, and calls to its methods are being delegated via the table class (see Table::__call()), unless a method with the same name already exists on the table class, which in your case means that _processDelete() will never be invoked.
I'd suggest that you study a little more on PHP/OOP basics, as this is rather basic stuff that can be untangled easily by just having a look at the source. Being able to understand how the CakePHP code base and the used concepts do work will make your life much easier.

Resources