phpunit not finding model method (Laravel / Mockery) - laravel

I'm getting started with unit testing in Laravel 4, and I'm stuck testing a custom method in a model I've added to the standard User model.
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends BaseModel implements UserInterface, RemindableInterface {
/**
* Logs to the database and streams an update
* Uses logIt and streamIt on Base model
* #param String $action The action being performed
* #return void
*/
private function logAndStream($action)
{
$this->logIt('info', $action.'d user '.$this->username);
$this->streamIt($action.'d user '.$this->username);
}
This class extends the BaseModel which in turn extends Eloquent, and has the defines the logIt and StreamIt methods like so:
class BaseModel extends Eloquent {
/**
* Log an action to log file
* #return void
*/
protected function logIt($level, $msg) {
...
}
/**
* Log an action to activity stream
* #return void
*/
protected function streamIt($msg, $client = null, $project = null) {
...
}
All of this code works fine when I'm manually testing things. But now I want to create a unit test to automate it.
class UserTest extends TestCase {
public function testLogAndStream()
{
$base = Mockery::mock('BaseModel')->shouldAllowMockingProtectedMethods();
$base->shouldReceive('logIt')
->with('info', 'Created user Tester')
->once();
$user = new User;
$user->username = 'Tester';
$user->logAndStream('Create');
}
When I try running this, I get a failure complaining about not finding logAndStream.
1) UserTest::testLogAndStream
BadMethodCallException: Call to undefined method Illuminate\Database\Query\Builder::logAndStream()
What am I missing?

You have two problems here:
First, your User model contains a logAndStream method, but it's private. This means that doing this:
$user->logAndStream('Create');
is not possible because only public methods are accessible in this way.
Second, in your test, you are mocking an instance of BaseModel. This has nothing to do with the instance of User that you instantiate a couple of lines later. User extends BaseModel - you can still have instances of User and BaseModel that are not related to one another. Here's an analogy:
class Database {
}
class DatabaseWithExtraFeatures extends Database {
}
The first one (Database) is just a plain old database access class and works just fine for basic stuff. Then someone comes along and realizes that Database doesn't provide some extra feature, so they build upon it by extending it. A developer can use either, or even both, in the same application.
$db = new Database;
$dbExtra = new DatabaseWithExtraFeatures;
// do something with $db
$result1 = $db->query();
// do something with $dbExtra
$result2 = $dbExtra->extraSpecialQuery();
What you've done in your test is analogous to this - you've mocked one instance of BaseModel and then instantiated a User class (which just happens to extend BaseModel).
EDIT Here's a little more detail on how to mock the user model:
$user = Mockery::mock('User')->shouldAllowMockingProtectedMethods();
$user->shouldReceive('logIt')
->with('some arguments')
->once();
$user->shouldReceive('streamIt')
->with('some arguments')
->once();
// when you set a property on an Eloquent model, it actually calls the
// setAttribute method. So do this instead of $user->username = 'Tester'
//
$user->shouldReceive('setAttribute')
->with('username', 'Tester')
->once()
$user->logAndStream('Create');
// now assert something...
Because User inherits from BaseModel, the methods from BaseModel are actually methods on User and can be treated as if they were defined as part of User.

Disclaimer: This was extracted from the question.
Firstly, I should have been checking that logIt and streamIt where observed on the same, mocked User model and not the parent BaseModel.
Populating the mock user model with $user->username was not correct either. In the end, Kryten helped me realise that Eloquent internally calls getAttribute('username') for this anyway, so I can return the value directly as part of an assertion, which would then be fed into logIt and StreamIt. This works but feels a little clunky - if anybody can suggest a better way, I'd love to learn.
Here's the working test case which works irrespective of whether logAndStream is declared as either a public or protected:
public function testLogAndStream()
{
$user = Mockery::mock('User');
$user->shouldReceive('getAttribute')
->with('username')
->atLeast()->once()
->andReturn('Tester');
$user->shouldReceive('logIt')
->once()
->with('info','Created user Tester');
$user->shouldReceive('streamIt')
->once()
->with('Created user Tester');
$user->logAndStream('Create');
}

Related

Laravel Mutator to add predefined values into database

I'm new into Laravel and I'm trying to store the user's company id on a column of the products table each time a user creates a new product. The company's id it's retrieved from the user's session. I'm trying it with Laravel's Mutator:
public function setFirstNameAttribute($value) {
$this->attributes['company_id'] = session()->get('company.id');
}
But each time I create a new Product the company id stored it's null. Seems like the function it's never executing. Is there any other resource to perform actions like this?
You must use model events - this will be executed on model creation before saving. Or you can use another events depends on you logic - see docs.
class YourModel extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::creating(function (YourModel $model) {
$model->company_id = session()->get('company.id');
});
}
}
Mutators only works when you change mutating field directly:
$model->first_name = 'new_name'
And with your code - you will lost "new_name".
I noticed that the function name is incorrect, since the accessors use "studly" cased name of the column you wish to access, it may be as simple as to change
public function setFirstNameAttribute($value)
to
public function setCompanyIdAttribute($value)

How to change the using of the pattern repository loaded using interfaces for initializing the model? Laravel, Laracom

Good day.
A store was created based on https://github.com/Laracommerce/laracom on Laravel.
In the process, it was noticed that, along with pulling up the implementation for the interface with a call like:
use App\Products\Repositories\Interfaces\ProductRepositoryInterface;
the binding of which is declared in RepositoryServiceProvider (app \ Providers \ RepositoryServiceProvider.php),
direct calls like use App\Shop\Products\Repositories\ProductRepository are used;
(e.g. here app/Shop/Orders/Repositories/OrderRepository.php)
You can find several similar examples in the code, and most often a direct address is required to invoke
$repositoryWithModel = new Repository($modelObject).
I did not find a definite way out of this situation, I ask the advice of those who came across an example of quality implementation.
The implementation of your ProductRepository expects a Product as constructor parameter. A respository should not do that. Instead if a repository has to handle a product model, it should be passed as a parameter to a function.
For example this:
/**
* #param Brand $brand
*/
public function saveBrand(Brand $brand)
{
$this->model->brand()->associate($brand);
}
Can be rewritten to:
/**
* #param Product $product
* #param Brand $brand
*/
public function saveBrand(Product $product, Brand $brand)
{
$product->brand()->associate($brand);
}
If you remove the Product parameter from the constructor, then you can use the repository without creating it using the new keyword every time:
class BrandController extends Controller {
public function __construct(ProductRepositoryInterface $repository) {
$this->repository = $repository;
}
public function linkBrandToProduct(Request $request): void {
$product = $this->repository->findProductById($request->productId);
$brand = $this->repository->findBrandById($request->brandId);
$this->repository->saveBrand($product, $brand);
}
}

Laravel - Redirect on model constructor

I have a Laravel project with some model which have been pointed to external databases (from Magento). On the constructor of this models I want to check a session variable in order to change the model connection to the user selection. One of this checks must be if the user selected some external platform, for connecting to this, if not I wanna redirect to the selection page. I am trying with this to avoid session expiration and manual route writing (without platform selection).
How can I redirect the user to certain page in the model constructor?
Main Model
class MagentoModel extends Model
{
protected $platform;
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->platform = app('selectedPlatform');
if ($this->platform->id < 1) {
return redirect()->route('index');
// Maybe I have to use Redirect::route('index');
}
}
}
Certain model extending MagentoModel
class Customer extends MagentoModel
{
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->connection = $this->platfom->connection_key;
$this->table = config('customersTable');
}
}
It throws an error right now:
Trying to get property 'connection_key' of non-object
I supose the parent constructor (MagentoModel) is not being executed before the line $this->connection = $this->platfom->connection_key;. Is possible my OOP comprehension is not right just in this case.

Laravel 5.3 soft deletes not working when defining a constructor for the model

I have a model Test as follows
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Test extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function __construct() {
if (!\App::environment('production')) {
$this->table='test_stage';
}
}
I have made sure that there is a 'deleted_at' column in my test_stage table. But the soft deletes are not working. Using the delete() method permanently removes the record from the table. As an additional step of verification I manually added 'deleted_at' value for some columns. But query the model still gives me the soft deleted record.
Moreover, removing the model constructor entirely and simply defining the table name using:
protected $table = 'test_stage';
Works like a charm! That is soft deletes magically start working again.
Or is there any way around to define the table name according to the environment without the need of defining a constructor?
I think the problem could be that you're overwriting the constructor, which is set in Illuminate\Database\Eloquent\Model. Have you tried
public function __construct(array $attributes = []) {
parent::__construct($attributes);
if (!\App::environment('production')) {
$this->table='test_stage';
}
}
Edit: more detailed explaination
As you overwrite the constructor of the class you're extending, the original does not get executed anymore. This means necessary functions for the eloquent model do not get executed. See the constructor for Illuminate\Database\Eloquent\Model below:
/**
* Create a new Eloquent model instance.
*
* #param array $attributes
* #return void
*/
public function __construct(array $attributes = [])
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
By making sure the extending class requires the same parameters for the constructor as the extended class and executes parent::__construct($attributes); first, the constructor of the extended class gets executed first. After which you can overwrite $this->table in the extending class.

Adding attributes to a model

I have four columns in my users database and a config variable. In my view I need to show the sum of those columns divided by the variable - simple enough to calculate but ideally I'd like the result of that to be stored as an attribute on the model.
Basically to avoid doing this:
$user = Auth::user();
$user->setPercent();
dd($user->percent);
What I'd like to do is have that percent attribute set automatically but I'm not sure where to do that. I've tried overriding the model constructor but that the data for the user hasn't been retrieved at that point.
Thanks
To create you just have to add a method like this to your user model:
class User extends BaseModel implements UserInterface, RemindableInterface {
public function getPercentAttribute($value)
{
/// do your magic
return $yourMagicValue
}
}
To use it the way you said you need:
$user->percent
And if you want to have this attribute when the object is created do:
public function __construct($attributes = array())
{
if (!isset($this->percent))
$this->percent = $this->setPercent();
parent::__construct($attributes);
}
This works for me

Resources