I'm testing a job that receives a customer model. In the job I create the
customer in shopify and store that id on the customer model. Then I call a sendShopifyInvite that needs to be mocked (I don't want to send an email in my test).
My test looks like this:
/** #test */
public function a_shopify_customer_is_created_if_it_does_not_yet_exists()
{
$this->partialMock(User::class, function ($mock) {
$mock->shouldReceive('sendShopifyInvite')->once()->andReturn(new User());
});
$customer = app(User::class)->fill(
factory(User::class)->create([
'shopify_id' => null
])->toArray()
);
$this->assertNull($customer->shopify_id);
CreateCustomerJob::dispatchNow($customer);
$customer->refresh();
$this->assertNotNull($customer->shopify_id);
}
The problem is that I receive this error:
PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'jensssen_db.mockery_0__domain__customer__models__users' doesn't exist
Is it not possible to persist data in a mock object? Are there any other ways?
Your problem is due to this line. Since a partial mocks creates a new mock object and calls your original model through it, it will take that class basename.
return $this->table ?? Str::snake(Str::pluralStudly(class_basename($this)));
I can see two solutions, i don't feel like anyone is the perfect solutions, it will solve your problem. Firstly set your table hardcoded on the User.php model. This will avoid the class basename being called.
class User {
$protected table = 'users';
}
Another approach, i have been forced to make before (when you have a hard time mocking some classes). Is instead of mocking your User.php model, simply put the same logic in a service / proxy class and mock that instead.
class ShopifyService {
public function sendInvite(User $user)
{
...
}
}
In your User.php model now have.
public function sendShopifyInvite() {
resolve(ShopifyService::class)->sendInvite($this);
}
Now you are able to mock only the Shopify service and now not tinker with the inner workings of the Eloquent Model.
$this->partialMock(ShopifyService::class, function ($mock) {
$user = new User();
$mock->shouldReceive('sendInvite')->with($user)->once()->andReturn($user);
});
Related
I'm trying to test a trait which is used in a few Eloquent models in my application. The testing approach I'm taking is making an anonymous class in my test class and testing that the trait method invoked on the anonymous class returns the correct results. I'm running into issues due to relationships being called in the trait method with which the anonymous class approach seems to be struggling.
I've created an anonymous class in my test class which extends the Eloquent model and uses my trait I'm trying to test. I've also specified the relationship in the anon class which is then used in the trait method I'm trying to test. However, since the relationship is many to many, when I try and call the relationship in the test, Eloquent is looking in a join table ai2nx3xla_emails (ai2nx3xla being the hidden name of the anon class) which of course doesn't exist in my database.
Relevant parts of the test class
public function setUp()
{
parent::setUp();
$this->testClass = new class extends Model {
use Contactable;
public function emails()
{
return $this->belongsToMany('App\Email')->withPivot('default');
}
};
}
/** #test */
public function it_can_return_the_default_email_for_a_model()
{
$email = factory(\App\Email::class)->make();
$this->testClass->setRelation('emails', $email);
$this->assertEquals($email->id, $this->testClass->defaultEmail());
}
Relevant parts of the trait (boiled down for simplicity)
abstract public function emails();
/**
* Return the default email if exists
*
*/
public function email()
{
$email = $this->emails()->wherePivot('default', true)->first() ?? $this->emails()->first();
return $email;
}
I'm expecting the test to pass but I'm actually getting this error message:
PDOException: SQLSTATE[42000]: Syntax error or access violation:
1064 You have an error in your SQL syntax; check the manual that >corresponds to your MySQL server version for the right syntax to use >near '.php0x10c63a39e_id as pivot_contactable_test.php0x10c63a39e_
id, `contactable' at line 1
My question would be, is there a way I can make this work in the current setup? Or should I approach this test in a different way? eg. testing a real model which uses the trait? Reluctant to do that though since if I remove the trait from that model the tests will fail. Any help would be greatly appreciated, thanks!
It's because queries done by Laravel are based on the classname (classnametolower_id) to get the foreign key but you can provide the table name as well i think this will resolve your problem :
new class extends Model {
use Contactable;
protected $table = 'yourtable';
public function emails()
{
return $this->belongsToMany('App\Email')->withPivot('default');
}
};
I have a little problem. What data keep in controllers and what in models? I know in models keep whole logic of applications etc, but what's query and helpers functions? for example
Controller:
public function add(Request $request)
{
$item = new Item()
$item->name = $request->name;
$item->save();
$this->makeDirectory();
}
private function makeDirectory()
{
//make a directory with photo this product
}
Where should I keep "makeDirecory" method in controller or models?
This is another situation when i would delete product and reference from another table.
public function delete(Items $id)
{
$id->delete();
$this->deleteProperties($id->properties); // $id->properties is a method from Items model with references to table Properties
}
private function deleteProperties(Properties $id)
{
$id->delete();
}
Should I keep "deleteProperties" method in controller, Items model or Properties model? and invoke this method from this model?
You should keep methods like makeDirectory() in a service class and call it with:
$this->fileService->makeDirectory($directory);
You should keep data related logic in model classes or repository classes and use it in controller with:
$this->model->getSomeData();
You may also want to google "Fat models, skinny controllers".
Regarding helper functions, you should use these only when you really need one. For example, isAdmin() is a very handy global helper, but you should never create helpers like getAllUsers() or Helpers::getAllUsers()
I use controllers only to validate the incoming data and passing data to views.
I add another layer of classes that I call Departments. So, I have a department for profiles, artiles, info pages etc. Each department has its own namespace and a set of classes connected with the functionality.
Always think about SoC - separation of concerns. If you put a lot of logic into a controller, it will eventually get huge, hard to maintain and extend.
Example:
Controller:
public function addItem (Request $request, Item $item, ItemStorage
$itemStorage) {
if ($item->verifyInput($request->all())) {
$itemStorage->createItem ($item, $request->all());
}
else {
// ... handle input error
}
// ... view
}
App\Departments\Items:
class ItemStorage {
public function createItem ($newItem, $attributes) {
$newItem->create($attributes);
// ... prepare data for creating a directory
$this->makeDirectory($directoryName);
}
private function makeDirectory ($directoryName) {
//... create directory
}
}
You can/should separate the tasks even further. ItemStorage might not need to handle actual directory creation. You can call another department/service class name e.g. DiskManagement. This department would contain Classes like FileSystem. So, inside the makeDirectory() method, you would call a method from a class specialized in file system operations.
In a small project with Laravel 5.3 and Stripe, I am trying to force create a Subscription on a User through a hasOne relationship:
// User.php
public function subscription() {
return $this->hasOne('App\Subscription');
}
public function subscribe($data) {
return $this->subscription()->forceCreate(
// $data contains some guarded fields;
// create() will simply ignore them...
);
}
However, I get :
Call to undefined method Illuminate\Database\Query\Builder::forceCreate()
Even though forceCreate() is a valid Eloquent method.
Any ideas how I can simulate this behavior? Or should I just save a Subscription manually assigning each field? The complication is that certain fields should be kept guarded, e.g. stripe_id.
EDIT
My quick' n' dirty solution:
// User.php # subscribe($data)
return (new Subscription())
->forceFill([
'user_id' => $this->id,
// $data with sensitive guarded data
])
->save();
I'm sure there is a better way though!
The call to $this->subscriptions() returns an instance of HasMany class, not a Model.
Contrary to create and createMany, there's no forceCreate() implemented in HasOneOrMany class. And thus, there's no first-hand API to use in such situations.
You see? when you call $this->subscriptions()->create(), you're calling the create method on a HasMany instance, not a Model instance. The fact that Model class has a forceCreate method, has nothing to do with this.
You could use the getRelated() method to fetch the related model and then call forceCreate() or unguarded() on that:
public function subscribe($data) {
return $this->subscription()
->getRelated()
->forceCreate($data);
}
But there's a serious downside to this approach; it does not set the relation as we're fetching the model out of the relation. To work around it you might say:
public function subscribe($data)
{
// $data['user_id'] = $this->id;
// Or more generally:
$data[$this->courses()->getPlainForeignKey()] = $this->courses()->getParentKey();
return $this->courses()
->getRelated()
->forceCreate($data);
}
Ehh, seems too hacky, not my approach. I prefer unguarding the underlying model, calling create and then reguarding it. Something along the lines of:
public function subscribe($data)
{
$this->courses()->getRelated()->unguard();
$created = $this->courses()->create($data);
$this->courses()->getRelated()->reguard();
return $created;
}
This way, you don't have to deal with setting the foreign key by hand.
laravel/internals related discussion:
[PROPOSAL] Force create model through a relationship
I am currently facing a very interesting dilemma with my architecture and implementation.
I have an interface called ServiceInterface which have a method called execute()
Then I have two different implementations for this interface: Service1 and Service2, which implements the execute method properly.
I have a controller called MainController and this controller has a "type-hint" for the ServiceInterface (dependency injection), it means that both, Service1 and Service2, can be called as resolution for that dependency injection.
Now the fun part:
I do not know which of those implementations to use (Service1 or Service2) because I just know if I can use one or other based on a user input from a previous step.
It means the user choose a service and based on that value I know if a can use Service1 or Service2.
I am currently solving the dependency injection using a session value, so depending of the value I return an instance or other, BUT I really think that it is not a good way to do it.
Please, let me know if you faced something similar and, how do you solve it, or what can I do to achieve this in the right way.
Thanks in advance. Please let me know if further information is required.
Finally, after some days of researching and thinking a lot about the best approach for this, using Laravel, I finally solved it.
I have to say that this was especially difficult in Laravel 5.2 because, in this version, the Session middleware only is executed in the controllers used in a route, it means that if for some reason I used a controller (not linked for a rote) and try to get access to the session it is not going to be possible.
So, because I cannot use the session, I decided to use URL parameters. Here you have the solution approach; I hope some of you found it useful.
so, you have an interface:
interface Service
{
public function execute();
}
Then a couple of implementations for the interface:
Service one:
class ServiceOne implements Service
{
public function execute()
{
.......
}
}
Service two.
class ServiceTwo implements Service
{
public function execute()
{
.......
}
}
The interesting part is that I have a controller with a function with a dependency with the Service interface. Still, I need to resolve it dynamically to ServiceOne or ServiceTwo based on user input. So:
The controller
class MyController extends Controller
{
public function index(Service $service, ServiceRequest $request)
{
$service->execute();
.......
}
}
Please note that ServiceRequest, validated that the request already have the parameter that we need to resolve the dependency (call it 'service_name')
Now, in the AppServiceProvider we can resolve the dependency in this way:
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
}
public function register()
{
//This specific dependency is going to be resolved only if
//the request has the service_name field stablished
if(Request::has('service_name'))
{
//Obtaining the name of the service to be used (class name)
$className = $this->resolveClassName(Request::get('service_name')));
$this->app->bind('Including\The\Namespace\For\Service', $className);
}
}
protected function resolveClassName($className)
{
$resolver = new Resolver($className);
$className = $resolver->resolveDependencyName();
return $className;
}
}
So now all the responsibility is for the Resolver class. This class basically use the parameter passed to the constructor to return the full name (with namespace) of the class that is going to be used as an implementation of the Service interface:
class Resolver
{
protected $name;
public function __construct($className)
{
$this->name = $className;
}
public function resolveDependencyName()
{
//This is just an example, you can use whatever as 'service_one'
if($this->name === 'service_one')
{
return Full\Namespace\For\Class\Implementation\ServiceOne::class;
}
if($this->name === 'service_two')
{
return Full\Namespace\For\Class\Implementation\ServiceTwo::class;
}
//If none, so throw an exception because the dependency can not be resolved
throw new ResolverException;
}
}
Well, I really hope it helps some of you.
Best wishes!
---------- EDIT -----------
I just realize that it is not a good idea to use the request data directly inside the container of Laravel. It really is going to cause some trouble in the long term.
The best way is to directly register all the possible instances supported (serviceone and servicetwo) and then resolve one of them directly from a controller or a middleware, so then is the controller "who decides" what service to use (from all the available) based on the input from the request.
In the end, it works at the same, but it is going to allow you to work more naturally.
I have to say thanks to rizqi, a user from the questions channel of the slack chat of Laravel.
He personally created a golden article about this. Please read it because it solves this issue completely and in a very right way.
laravel registry pattern
The fact that you define that your controller works with ServiceInterface is ok
If you have to choose the concrete implementation of the service basing on a previous step (that, as i've understood, happens in a previous request) storing the value in session or in database is right too, as you have no alternative: to choose the implementation you have to know the value of the input
The important point is to 'isolate' the resolution of the concrete implementation from the input value in one place: for example create a method that takes this value as a parameter and returns the concrete implementation of the service from the value:
public function getServiceImplementation($input_val)
{
switch($input_val)
{
case 1 : return new Service1();
case 2 : return new Service2();
}
}
and in your controller:
public function controllerMethod()
{
//create and assign the service implementation
$this->service = ( new ServiceChooser() )->getServiceImplementation( Session::get('input_val') );
}
In this example i've used a different class to store the method, but you can place the method in the controller or use a Simple Factory pattern, depending on where the service should be resolved in your application
It's an interesting problem. I'm currently using Laravel 5.5 and have been mulling it over. I also want my service provider to return a specific class (implementing an interface) based upon user input. I think it's better to manually pass the input from the controller so it's easier to see what's going on. I would also store the possible values of the class names in the config.
So based upon the Service classes and interface you've defined above i came up with this:
/config/services.php
return [
'classes': [
'service1' => 'Service1',
'service2' => 'Service2',
]
]
/app/Http/Controllers/MainController.php
public function index(ServiceRequest $request)
{
$service = app()->makeWith(ServiceInterface::class, ['service'=>$request->get('service)]);
// ... do something with your service
}
/app/Http/Requests/ServiceRequest.php
public function rules(): array
$availableServices = array_keys(config('services.classes'));
return [
'service' => [
'required',
Rule::in($availableServices)
]
];
}
/app/Providers/CustomServiceProvider.php
class CustomServiceProvider extends ServiceProvider
{
public function boot() {}
public function register()
{
// Parameters are passed from the controller action
$this->app->bind(
ServiceInterface::class,
function($app, $parameters) {
$serviceConfigKey = $parameters['service'];
$className = '\\App\\Services\\' . config('services.classes.' . $serviceConfigKey);
return new $className;
}
);
}
}
This way we can validate the input to ensure we are passing a valid service, then the controller handles passing the input from the Request object into the ServiceProvider. I just think when it comes to maintaining this code it will be clear what is going on as opposed to using the request object directly in the ServiceProvider.
PS Remember to register the CustomServiceProvider!
I find the best way to deal with this is using a factory pattern. You can create a class say ServiceFactory and it has a single method create() it can accept an argument which is used to dynamically choose which concrete class to instantiate.
It has a case statement based on the argument.
It will use App::make(ServiceOne::class) or App::make(ServiceTwo::class).depending on which one is required.
You are then able to inject this into your controller (or service which depends on the factory).
You can then mock it in a service unit test.
Recently, I had to implement a similar logic where I was to implement a method to perform mobile top-ups for multiple networks in our application. So, I decided to implement the logic using Factory and Bridge pattern. Factory to create an instance of the concrete Service class based on the user input, and then, the Bridge pattern to set closely related classes into separate hierarchies and route the request to the respective class.
In the controller's method, both Factory and Service classes are injected. The TopUpServiceFactory's create method creates an object of the concrete class. The TopUpService class then routes the request to that concrete class method.
class TopUpController extends Controller
{
public function topUp(Request $request, TopUpServiceFactoryInterface $serviceFactory, TopUpServiceInterface $topUpService)
{
$serviceFactory->create($request->networkCode);
$topUpService->TopUp($request->all());
}
}
The TopUpServiceFactoryInterface and TopUpServiceInterface are bound to TopUpServiceFactory and TopUpService concrete Classes respectively in Service Container.
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(TopUpServiceFactoryInterface::class, TopUpServiceFactory::class);
$this->app->bind(TopUpServiceInterface::class, TopUpService::class);
}
}
The create method accepts user input and creates an object of the respective class based on the user input.
class TopUpServiceFactory implements TopUpServiceFactoryInterface
{
public function create(string $networkCode)
{
switch ($networkCode) {
case 'network1':
app()->bind(NetworkServiceInterface::class, Network1Service::class);
break;
case 'network2':
app()->bind(NetworkServiceInterface::class, Network2Service::class);
break;
default:
app()->bind(NetworkServiceInterface::class, DefaultNetworkService::class);
break;
}
}
}
The Service Class then picks the object of NetworkService Class and forwards the request.
class TopUpService implements TopUpServiceInterface
{
public function topUp(array $requestParams)
{
$networkService = app()->get(NetworkServiceInterface::class);
$networkService->topUp($requestParams);
}
}
All network's concrete classes implement a common interface NetworkServiceInterface, which is used to inject dependency dynamically, implementing Liskov Substitution Principle
class Network1Service implements NetworkServiceInterface
{
public function topUp(array $requestParam)
{
Process Topup ......
}
}
class Network2Service implements NetworkServiceInterface
{
public function topUp(array $requestParam)
{
Process Topup ......
}
}
...
I'm trying to create some tests.
Here's my Test Class:
class ExampleTest extends TestCase {
public function setUp()
{
parent::setUp();
Artisan::call('migrate');
$this->seed();
Auth::loginUsingId(1);
}
public function testActionUpdateNew()
{
$action = new Action(Array());
$action->save();
var_dump($action->id);
Action::with('reponses','contact','user','etudiant','entreprise','etude')->findOrFail($action->id);
}
public function testEtudes()
{
$etudes=Etude::all()->toArray();
$this->assertCount(10, $etudes, "Nombre d'études incorrectes");
$numEtudes=count($etudes);
//Buggy part
$etude= Etude::create(Array());
var_dump($etude->id);
$etudes=Etude::all()->toArray();
$this->assertCount(11, $etudes, "Nombre d'études incorrectes");
//10+1 should equal to 11 but it hasnt updated
}
}
The test that is not passing is the second one: I count the number of eloquent Objects Etudes, which are of 10 at the beginning, I then add one etude to the database (using Etude::create()) , the object is created, because $etude->id gives out a real number. Howewer, the number of Etude hasn't updated.
The problem does go away when I remove the 'etude' from the eager loading in Action::with('reponses',...)
Here is the etudes relationship in the Action class:
public function etude() {
return $this->belongsTo('Etude');
}
Do you guys have any idea if eager-loading in laravel can have such strange behavior and how to fix that ?
EDIT
I found out that calling with('etude') had the action to remove the events registered to the Eloquent Model:
boot Method of Etude:
public static function boot()
{
parent::boot();
static::creating(function($etude)
{
var_dump("creating etude"); //This doesn't get executed even when I run Etude::create(Array());
}
);
}
So If I add Etude::boot() at the beginning of testEtudes, it works again. This is still strange.
Does eager loading has any effect on events or the boot method ? Or is the boot method not called automatically after each test ?
In Laravel tests, the event dispatcher is reset between each test, but the models are still only booted once as they live a pretty independent life. This means that between each test, the model listeners are erased but never re-registered. The solution is to not use boot() for registering model events, but rather but them in a separate file - either a service provider or a file included from app/start/global.php (app/events.php is a common one).