Laravel test Mockery - laravel

Laravel: 6.x
PHP 7.3
I'am wirte a Repositorytest. below is my code, how can I fix this error,
error
TypeError: Return value of Mockery_5_App_Repositories_PersonRepository::fetchOrNewPersonById() must be an instance of App\Models\PersonModel, instance of Mockery\CompositeExpectation returned
Repository
class ContactRepository implements ContactRepositoryInterface
{
private $contact;
private $personRepo;
public function __construct(Contact $contact)
{
$this->contact= $contact;
$this->personRepo= app()->make(PersonRepositoryInterface::class);;
}
public function updateContacts(array $datas): bool
{
foreach ($datas as $key => $value) {
$person = $this->personRepo->fetchOrNewPersonById($value['person_id']);
$person->fill($value);
if( !$person->save()){ return false; }
}
return true;
}
}
and
class PersonRepository implements PersonRepositoryInterface
{
private $personModel;
public function __construct(Person $personModel)
{
$this->personModel= $personModel;
}
public function fetchOrNewPersonById($id): PersonModel
{
return $this->personModel->firstOrNew(['id' => $id]);
}
}
..which implements a Model.
class PersonModel extends Model
{
protected $table = 'person';
}
and
class Contact extends Model
{
protected $table = 'contacts';
}
my testing code
class ContactRepositoryTest extends \Myname\Myapptests\TestCase
{
/**
* #test
*/
public function test_can_update()
{
$personMock = Mockery::mock(PersonModel::class)
->shouldReceive('fill')
->shouldReceive('save')
->andReturn(true);
$this->app->instance(PersonModel::class, $personMock);
$personRepoMock = Mockery::mock(PersonRepository::class)
->shouldReceive('fetchOrNewPersonById')->andReturn($personMock)
->getMock();
$this->app->bind(ContractRepositoryInterface::class, function () use ($contractRepoMock) {
return $contractRepoMock;
});
$modelMock = Mockery::mock(Contact::class)
$service = new class($modelMock) extends ContactRepository {};
$service->updateContacts([
['person_id' => 3, 'address' => ' XXXXXX update address'],
['person_id' => null, 'address' => ' XXXXXX create address'],
]);
$this->assertTrue(true);
}
}
Obviously I am missing something. Other examples explicitly inject the mocked object into the class they are then testing. Laravels IoC is (should be) doing this for me. Do I have to bind anything?

Related

Singleton Service does not work as expected

The singleton instance that injected through the service provider is not remain between Requests, which it should. Maybe it's because of my implementation, so can you help me check it:
I has a interface & a class that implements it:
namespace App\Interfaces\CategoryRepositoryInterface;
interface CategoryRepositoryInterface
{
public function testSingleton($value = null);
}
......
namespace App\Repositories
use App\Interfaces\CategoryRepositoryInterface;
class CategoryRepository implements CategoryRepositoryInterface
{
private ?string $value = null;
public function testSingleton($value = null)
{
if ($value != null) {
$this->value = $value;
}
return $this->value;
}
}
Then I bind it with the service provider, like this:
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(
CategoryRepositoryInterface::class,
CategoryRepository::class
);
}
Then I injects it in the controller action. Take a look at the detail() function, when I redirected it from list(), the value that set to the $catRepo was lost because it value is Null at this point.
class CategoryController extends Controller
{
public function list(CategoryRepositoryInterface $catRepo, Category $cat) {
$catRepo->testSingleton('value is set');
$check = $cat->repo->testSingleton();
return to_route('category.detail');
}
public function detail(CategoryRepositoryInterface $catRepo) {
$check = $catRepo->testSingleton();
}
}
So, if there is nothing wrong with how the singleton to work this way, what is the difference between singleton and scoped?
Thank you very much guys for helping me!

Call to undefined relationship on model

We have the following class using $with:
class CargaHorasEmpleado extends Model
{
protected $table = "empleados_horas";
protected $with = ["tipoTarea", "proyecto", "empleado", "empleadoQueHizoLaCarga"];
public function tipoTarea()
{
return $this->belongsTo('App\TipoTarea', 'id_tipo_tarea', 'id')->withTrashed();
}
public function empleado()
{
return $this->belongsTo('App\Empleado', 'id_empleado', 'id')->withTrashed();
}
public function empleadoQueHizoLaCarga()
{
return $this->belongsTo('App\Empleado', 'id_empleado_cargo_hs', 'id')->withTrashed();
}
public function proyecto()
{
return $this->belongsTo('App\Proyecto', 'id_proyecto', 'id')->withTrashed();
}
}
This is the class TipoTarea
namespace App;
use Illuminate\Database\Eloquent\Model;
class TipoTarea extends Model
{
protected $table = 'tipos_tareas';
public $timestamps = false;
protected $fillable = [
'titulo', 'descripcion'
];
}
Thep page throws the error: "Call to undefined relationship [tipoTarea] on model [App\CargaHorasEmpleado]". That's the only relationship that's not working. The others are fine. What's wrong?
Well, isn't the relationship called "tipoTarea"? You wrote "tiposTarea"
The problem was that my class "TipoTarea" didn't use softdeletes. So the error was in using the "WithTrashed" method. The correct way is:
public function tipoTarea()
{
return $this->belongsTo('App\TipoTarea', 'id_tipo_tarea', 'id');
}

Use Factory Pattern in Laravel

I am working on a Laravel project and want to implement Factory and Repository pattern in my project. However, I still don't know where should I use Factory pattern? It's still very confusing. I understand that it's not necessary to use the Factory pattern for every single project but as a part of my study, I want to learn more about the combination of this 2 patterns. This is my code example. Hope I can get help and explains from you guys. Thank you.
class ProductFactory
{
public function create()
{
return new Product;
}
}
class ProductRepository implements ProductRepositoryInterface
{
protected $productFactory;
public function __contruct(
ProductFactory $productFactory
)
{
$this->productFactory = $productFactory
}
public function all()
{
return Product::all();
}
public function loadById($id);
{
return Product::find($id);
}
public function save($data,$id=NULL)
{
if($id != NULL){
$product = $this->loadById($id)
}
else{
$product = $this->productFactory->create();
}
return $product->fill($data)->save();
}
.....
}
I think you need to split the factory and repository. For example:
class ProductRepository implements ProductRepositoryInterface
{
protected $product;
public function __contruct(Product $product)
{
$this->product = $product;
}
//...
}
But not necessary for DI in repository, your methods like all(), find() and etc. put in AbstractRepository. Your ProductRepositry:
class ProductRepository extends AbstractRepository implements ProductRepositoryInterface
{
public function __contruct()
{
//model property in AbstractRepository
$this->model = new Product();
}
}
If you want to write test for it, you may use laravel container:
AppServiceProvider:
$this->app->bind(Product::class, function ($app) {
return new Product();
});
ProductRepository:
$this->model = app()->make(Product::class);
PS: i think factory useless, in most cases i use container. But in some difficult cases i use factory, for example:
class ProductFactory
{
public static function create(int $type, array $attributes): ProductInterface
{
switch ($type) {
case 'market':
return new MarketProductModel($attributes);
break;
case 'shop':
$model = new ShopProductModel($attributes);
$model->setMathCoef(1.2);
return $model;
break;
case 'something':
return new SomethingProductModel($attributes);
break;
}
}
}

Model Event not working

I have the following model:
class ProjectTwitterStatus extends Eloquent {
protected $table = 'project_twitter_statuses';
protected $softDelete = true;
protected $guarded = array('id');
public static function boot()
{
parent::boot();
ProjectTwitterStatus::deleting(function($projectTwitterStatus)
{
$projectTwitterStatus->deleted_by = Auth::user()->id;
});
}
public function twitterStatus() {
return $this->belongsTo('TwitterStatus');
}
public function twitterRetweet() {
return $this->belongsTo('TwitterRetweet','twitter_status_id','twitter_status_id');
}
public function project() {
return $this->belongsTo('Project');
}
}
Somewhere in my app an item is deleted using one of the following statements:
ProjectTwitterStatus::find($id)->delete();
ProjectTwitterStatus::whereIn('twitter_status_id', $twitterStatusIds)->delete();
I can see in the database the item had been (soft) deleted. But the deleted_by column is not filled.
Does anyone know what I am doing wrong?
Try using Late Static Binding, like following-
class ProjectTwitterStatus extends Eloquent {
public static function boot ()
{
parent::boot();
static::deleting(function($projectTwitterStatus)
{
$projectTwitterStatus->deleted_by = Auth::user()->id;
});
}
}

How can I select linked objects from multiple parent objects in Laravel Eloquent

I would like to do something like this.
Location::where('city', '=', 'Chicago')->chef();
With these relationships:
class Location extends Eloquent {
protected $table = 'locations';
public function chef() {
return $this->belongsTo('Chef');
}
}
class Chef extends Eloquent {
protected $table = 'chefs';
public function location() {
return $this->hasMany('Location');
}
}
This should work:
class Location extends Eloquent {
protected $table = 'locations';
public function chefs() {
return $this->belongsTo('Chef');
}
public function getAllChefsByCity($city)
{
$this->with('chefs')->where('city', $city)->get();
}
}
Then in your code:
$array = $location->getAllChefsByCity('Chicago');

Resources