I'm working with Laravel 5.2 and phpunit and i'm writing some test for my application. until now i got no problem, and today i encounter something weird and I can't find a way to handle it.
Some of my test file doesn't use transactions although the others does.
i use use DatabaseTransactions; in my TestCase class with is extended in every test file i got.
Most of my test works without any troubles but some of them does not.
Here is one which works withotut any troubles :
class V2LikeTest extends TestCase {
protected $appToken;
protected $headers;
public function setUp() {
parent::setUp();
$this->generateTopic(1);
}
/** LIKE TOPICS */
/** #test */
public function it_likes_a_topic() {
$job = new ToggleLikeJob(Topic::first());
$job->handle();
$topic = Topic::first();
$this->assertTrue($topic->present()->isLiked);
$this->assertEquals(1, $topic->nb_likes);
}
}
and this one with troubles:
class V2TopicTest extends TestCase {
private $appToken;
private $headers;
public function setUp() {
parent::setUp();
$this->generateCompany(1);
}
/** #test */
public function it_create_a_topic() {
$new_topic_request = new Request([
'content' => str_random(100),
'type' => Topic::TYPE_FEED_TEXT
]);
$job = new CreateFeedTopicJob($new_topic_request->all());
$job->handle();
$this->assertCount(1, Topic::all());
}
}
It's been a while now that i'm looking for the solution but not able to find it. Did someone already meet this troubles?
edit: GenerateTopic function use generateCompany just in case ;)
Related
This is my feature test
class CreateImageTest extends TestCase
{
private static function headers(){
....
}
/**
* #test
*
*/
public function no_api_key_404()
{
......
}
/**
* #test
*
*/
public function not_logged_in_401()
{
......
}
/**
* #test
*
*/
public function empty_body_422()
{
....
}
}
I've always begun the test with middleware tests like what you see above (auth and API key middleware). I'm going to use the same test procedure for endpoints with similar middleware (there are a lot of them). How can I do that without being redundant? I was thinking to make a trait for the recurring testing pattern but I have no idea how to do that.
I think you should be able to achieve what you want by introducing an AbstractTestCase file. This file could contain all of your repeating middleware tests and be extended by the Test classes that need it.
Your abstract class could look something like this:
<?php
abstract class AbstractMiddlewareTestCase extends TestCase
{
protected array $headers = [];
public function no_api_key_404()
{
$this->get('your_api_url', $this->headers)
->seeStatusCode(404);
}
// Add any other shared tests here
}
And then you could extend from your regular test file like so:
<?php
class CreateImageTest extends AbstractMiddlewareTestCase
{
use ApiKeyTrait;
public function create_image() {
$this->get('your_api_url', $this->headers)
->seeStatusCode(200);
}
}
Additionally, you could leverage Traits to cater to multiple types of Middleware. I.e. if your create image endpoint is api key authorised you could have an ApiKeyTrait that could look something like this:
<?php
trait ApiKeyTrait
{
public function setUp(): void
{
parent::setUp();
$this->headers = [
'X-API-KEY' => 'yourKey',
];
}
}
I'm looking to find a way to test Policy methods in isolation. The policies work as intended when Feature testing (e.g. via http) but I am having a hard time trying to write tests for these when I just want to test the logic of the policy itself.
My code:
class ItemPolicy
{
...
public function delete(User $user, Item $item)
{
return $this->can('delete_item') && !$item->isDeletable();
}
...
}
class ItemPolicyTest extends TestCase
{
use RefreshDatabase;
private $user;
public function setUp() : void
{
parent::setUp();
$this->user = User::factory()->create();
Permission::create(['name' => 'delete_item', 'guard_name' => 'web']);
$role = Role::create(['name' => 'admin', 'guard_name' => 'web']);
$role->givePermissionTo('delete_item');
$this->user->assignRole(['admin']);
$this->user = $this->user->fresh(); //just to make sure
$this->app->make(\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();
}
/** #test */
public function it_will_not_allow_deletion()
{
$item = Item::factory()->cannotBeDeleted()->create();
$this->be($this->user, 'web');
$this->assertFalse(auth()->user()->can('delete', $item));
}
}
Setting a breakpoint in my Policy doesn't get triggered so I assume that Spatie has taken this over somewhere. So ultimately, I'm looking for a way to recreate this so I can test these in isolation. Any help appreciated!
Writing some unit tests, and I want to have an object created before the tests in the class are done. So I set up the setUpBeforeClass() method:
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Location;
class UserTests extends TestCase {
const FAKEID = 9999999;
public static function setUpBeforeClass() : void {
parent::setUpBeforeClass();
factory(Location::class)->make(["id" => self::FAKEID])->save();
}
}
But when I try running this, I get this error:
InvalidArgumentException: Unable to locate factory with name [default] [App\Location].
But the factory class is set up properly. In fact, if I move this same line down to one of my test functions it works perfectly.
public function testCreateUser() {
factory(Location::class)->make(["id" => self::FAKEID])->save();
// do other stuff...
}
The only thing that sticks out to me as different about setUpBeforeClass() is that it's a static method, but I don't know why that would prevent the factory class from working.
Laravel does a lot of setting up in the setUp() method in the TestCase class. The setUpBeforeClass() method is called before that, that's why your factory is not loaded yet.
The Laravel's TestCase class setup method (see class):
/**
* Setup the test environment.
*
* #return void
*/
protected function setUp()
{
if (! $this->app) {
$this->refreshApplication();
}
$this->setUpTraits();
foreach ($this->afterApplicationCreatedCallbacks as $callback) {
call_user_func($callback);
}
Facade::clearResolvedInstances();
Model::setEventDispatcher($this->app['events']);
$this->setUpHasRun = true;
}
Change your code to use setUp instead:
protected static function setUp() : void
{
parent::setUp();
factory( Location::class )->make( ["id" => self::FAKEID] )->save();
}
I have read so many examples and cannot see what I am doing wrong, please if someone could help.
I am getting an error when running tests (error at the bottom of post), that doens't happen when viewing the page in the browser. I think this is because the repository isn't being instantiated properly so the relevant method not fired? Or some issue with the API call in the mock.
Controller:
namespace ShopApp\Http\Controllers\StoreFront;
use Illuminate\Http\Request;
use ShopApp\Http\Requests;
use ShopApp\Http\Controllers\Controller;
use ShopApp\Repositories\Contracts\CategoryRepositoryContract;
use ShopApp\Repositories\Contracts\PublicationRepositoryContract;
class PagesController extends Controller
{
private $publication;
private $category;
public function __construct(PublicationRepositoryContract $publication, CategoryRepositoryContract $category){
$this->publication = $publication;
$this->category = $category;
}
/**
* Homepage.
* #return view
* #internal param PublicationRepositoryContract $publication
* #internal param CategoryRepositoryContract $category
*/
public function home()
{
$mostRecent = $this->publication->getRecent();
return view('pages/home')->with(compact('mostRecent'));
}
}
Publication Repository:
<?php
namespace ShopApp\Repositories;
use ShopApp\Models\API\APIModel;
use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Support\Facades\Config;
use ShopApp\Repositories\Contracts\PublicationRepositoryContract;
class localPublicationRepository extends APIModel implements PublicationRepositoryContract
{
private $end_point; // where are we talking to?
public $response; //what did we get back?
public function __construct(GuzzleClient $client){
parent::__construct(new $client(['base_uri' => Config::get('customerprovider.local.api.base_uri'), 'http_errors' => true]));
$this->end_point = 'Publications';
}
/**
* Get all publications
*/
public function getAll(){
$this->response = $this->get($this->end_point);
$publications_with_slugs = $this->assembleSlugs($this->response);
return $publications_with_slugs;
}
/**
* Get recent publications
*/
public function getRecent(){
return $this->getAll(); //#todo - update this to just get the most recent
}
}
Test:
<?php
namespace Tests\Unit\Controllers;
use Tests\TestCase;
use Mockery as m;
class PagesControllerTest extends TestCase
{
public $publicationRepositoryContract;
/**
* Setup mocks etc
*/
public function setUp()
{
parent::setup();
$this->publicationRepositoryContract = m::mock('ShopApp\Repositories\Contracts\PublicationRepositoryContract');
}
/**
* Teardown mocks
*/
public function tearDown()
{
m::close();
parent::tearDown();
}
/**
* A basic test example.
*
* #return void
*/
public function testHomepage()
{
$this->publicationRepositoryContract
->shouldReceive('getRecent')
->once();
$this->app->instance('ShopApp\Repositories\Contracts\PublicationRepositoryContract', $this->publicationRepositoryContract);
$response = $this->call('GET', '/');
$response->assertStatus(200);
// getData() returns all vars attached to the response.
$mostRecent = $response->original->getData()['mostRecent'];
$response->assertViewHas('mostRecent');
$this->assertInstanceOf('Array', $mostRecent);
}
}
Test Error:
Expected status code 200 but received 500.
Failed asserting that false is true.
/home/vagrant/Code/imsnews-site/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:61
/home/vagrant/Code/imsnews-site/tests/Unit/Controllers/PagesControllerTest.php:53
Contents of Response ($response->Content()):
<span class="exception_title"><abbr title="ErrorException">ErrorException</abbr> in <a title="/home/vagrant/Code/imsnews-site/storage/framework/views/229655ca372490c9c0b1f5e7e2d4e91e6d3bbf6c.php line 262">229655ca372490c9c0b1f5e7e2d4e91e6d3bbf6c.php line 262</a>:</span>\n
<span class="exception_message">Invalid argument supplied for foreach() (View: /home/vagrant/Code/imsnews-site/resources/views/pages/home.blade.php)</span>\n
Line 262 from home.blade.php:
#foreach ($mostRecent as $key => $publication)
It seems clear that the method ->getRecent(), which in turn, calls ->getAll() on the publications repository is not returning an array as it should, but I don't know why.
Blade isn't complaining about the variable mostRecent not existing, it's complaining about it being invalid in a foreach.
Could this have something to do with Guzzle and the fact it's calling my API from the mocked test object?
Please help, hours have been lost..
Thanks.
Try mocking the concrete repository, and swap it out for the contract in the container. It seems you are mocking the contract, and then swapping it out for the same contract in your container.
TL;DR :
The key was you HAVE to have ->andReturn([]); on the test, like so:
$this->publicationRepositoryContract
->shouldReceive('getRecent')
->once()->andReturn([]);
My test only had:
$this->publicationRepositoryContract
->shouldReceive('getRecent')
->once();
Thanks to Ayo for pointing this out. It only became clear after deleting other parts of my test.
I'm trying to write a test class for a shopping cart. Here is what I have:
ShoppingCartTest.php
class ShoppingCartTest extends TestCase {
use DatabaseTransactions;
protected $shoppingCart;
public function __construct() {
$this->shoppingCart = resolve('App\Classes\Billing\ShoppingCart');
}
/** #test */
public function a_product_can_be_added_to_and_retrieved_from_the_shopping_cart() {
// just a placeholder at the moment
$this->assertTrue(true);
}
}
However, when I run phpunit, it seems like Laravel is unable to resolve my ShoppingCartClass.
Here is the error:
Fatal error: Uncaught exception 'Illuminate\Contracts\Container\BindingResolutionException'
with message 'Unresolvable dependency resolving
[Parameter #0 [ <required> $app ]] in class Illuminate\Support\Manager'
in C:\Development Server\EasyPHP-Devserver-16.1\eds-www\nrponline\vendor\laravel\framework\src\Illuminate\Container\Container.php:850
I have my ShoppingCart class being resolved in a number of different controllers just fine.
Why can't Laravel resolve it during my tests?
I refered to this post as well but still didn't have any luck.
I figured it out. Here is the updated class.
class ShoppingCartTest extends TestCase {
use DatabaseTransactions;
protected $shoppingCart;
public function setUp() {
parent::setUp();
$this->shoppingCart = $this->app->make('App\Classes\Billing\ShoppingCart');
}
/** #test */
public function a_product_can_be_added_to_and_retrieved_from_the_shopping_cart() {
// just a placeholder at the moment
$this->assertTrue(true);
}
}
Thanks to #edcs for guiding me in the right direction.
You need to use a setUp function and not __construct as the app instance hasn't been created yet.
If you want to use __construct you have to use the same constructor of PHPUnit\Framework\TestCase and remember to call the parent method if you don't want to break anything
class MyTest extends TestCase
{
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
// my init code
}
}
However the proper way would be to use the method setUpBeforeClass() if you want to execute your init code once or setUp() if you want to execute the init code before each test contained in your class.
Check PHPUnit documentation for more details.