I'm using $entityManager->getReference() to get a proxy for my entity. When I call the getter for the primary key Doctrine will intialize the proxy and execute a SELECT query. I expect this only to happen when calling other properties which are not yet known by Doctrine.
The code below illustrates the issue:
$myEntity = $em->getReference('MyNamespace\MyEntity', 1);
$myEntity->getId(); //This will execute a database call. I expect it to just return 1
The getter in the lazy loading proxy looks like this:
public function getId()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', array());
return parent::getId();
}
Is this intended behaviour or did I overlook something?
Related
// Throw an Error While I have Already Initialized? Please need Help
public function mount()
{
$this->user_model = new User();
$this->userData = $this->user_model->displayUsers();
$this->role_model = $role_model ?? new Role();
$this->roleData = $this->role_model->displayRoles();
}
Try to set default values in your property definitions.
class YouLivewireClass extends Component
{
public string $email = "";
public int $number = 0;
public ?User $user = null;
public function mount()
{
// your logic
}
}
mount() method in Livewire is not the same as a __construct method.
It is called very early but the object is already created and some of the general PHP checks are executed. If you define strict types for the properties that can result in error in Livewire, because they can not fallback to null for the initialisation process.
Here a nice explanation from the Livewire forum: https://forum.laravel-livewire.com/t/1-0-3-typed-property-must-not-be-accessed-before-initialization/320
For anyone wondering why: With the typed properties in 7.4 they have a
state of initialized and uninitialized, instead of the expected
behavior of properties defaulting to a null value if there isn’t one
assigned. In “regular” classes, the properties can maintain an
uninitialized state, and this error is only thrown when you try to
actually access it. Which all makes sense because the point of it all
is to have stricter code.
There’s a few reasons why Livewire (and a portion of Laravel for that
matter) is different than a textbook oop object. First, mount() should
not be thought of as __construct(), and should be thought of closer to
Laravel’s boot() method. The object has been built already, and some
behind the scenes work has been done that makes Livewire work, before
the mount() method is called. mount() is just the first method called
in in the code you provide to Livewire.
All the examples of jest and jest-fetch-mock I have come across use functions that perform an API query and return a payload directly from the function call.
In my case, I have a different setup. I have a class that has a property called 'data'. In the class there is a method called "get" which pulls data using fetch API and stores it in the data property. When the method is called, it simply returns true or false based on promise resolve or reject.
I am trying to figure out how to write unit tests for this in this case. My function doesn't return the data fetched; only a boolean value.
So if I use jestSpyOn to mock the class method, how would I set the data property, and then retrieve the result?
In my code, I do something like this (NOT in testing, but in the actual app):
contactStore = new ContactListStore();
// 'all' is a sample param passed
contactStore.get('all').then(res => {
if(res){
...perform action
}
});
As you can see the res argument is only boolean, and if true, then contactStore.data will contain the information retrieved from the server.
So to run a unit test on it, I need to call a mock get, and set a mock data property.
Any ideas how this would be done?
In your mock method, you just need return true.
I'm mocking my repository correctly, but in cases like show() it either returns null so the view ends up crashing the test because of calling property on null object.
I'm guessing I'm supposed to mock the eloquent model returned but I find 2 issues:
What's the point of implementing repository pattern if I'm gonna end up mocking eloquent model anyway
How do you mock them correctly? The code below gives me an error.
$this->mockRepository->shouldReceive('find')
->once()
->with(1)
->andReturn(Mockery::mock('MyNamespace\MyModel)
// The view may call $book->title, so I'm guessing I have to mock
// that call and it's returned value, but this doesn't work as it says
// 'Undefined property: Mockery\CompositeExpectation::$title'
->shouldReceive('getAttribute')
->andReturn('')
);
Edit:
I'm trying to test the controller's actions as in:
$this->call('GET', 'books/1'); // will call Controller#show(1)
The thing is, at the end of the controller, it returns a view:
$book = Repo::find(1);
return view('books.show', compact('book'));
So, the the test case also runs view method and if no $book is mocked, it is null and crashes
So you're trying to unit test your controller to make sure that the right methods are called with the expected arguments. The controller-method fetches a model from the repo and passes it to the view. So we have to make sure that
the find()-method is called on the repo
the repo returns a model
the returned model is passed to the view
But first things first:
What's the point of implementing repository pattern if I'm gonna end up mocking eloquent model anyway?
It has many purposes besides (testable) consisten data access rules through different sources, (testable) centralized cache strategies, etc. In this case, you're not testing the repository and you actually don't even care what's returned, you're just interested that certain methods are called. So in combination with the concept of dependency injection you now have a powerful tool: You can just switch the actual instance of the repo with the mock.
So let's say your controller looks like this:
class BookController extends Controller {
protected $repo;
public function __construct(MyNamespace\BookRepository $repo)
{
$this->repo = $repo;
}
public function show()
{
$book = $this->repo->find(1);
return View::make('books.show', compact('book'));
}
}
So now, within your test you just mock the repo and bind it to the container:
public function testShowBook()
{
// no need to mock this, just make sure you pass something
// to the view that is (or acts like) a book
$book = new MyNamespace\Book;
$bookRepoMock = Mockery::mock('MyNamespace\BookRepository');
// make sure the repo is queried with 1
// and you want it to return the book instanciated above
$bookRepoMock->shouldReceive('find')
->once()
->with(1)
->andReturn($book);
// bind your mock to the container, so whenever an instance of
// MyNamespace\BookRepository is needed (like in your controller),
// the mock will be loaded.
$this->app->instance('MyNamespace\BookRepository', $bookRepoMock);
// now trigger the controller method
$response = $this->call('GET', 'books/1');
$this->assertEquals(200, $response->getStatusCode());
// check if the controller passed what was returned from the repo
// to the view
$this->assertViewHas('book', $book);
}
//EDIT in response to the comment:
Now, in the first line of your testShowBook() you instantiate a new Book, which I am assuming is a subclass of Eloquent\Model. Wouldn't that invalidate the whole deal of inversion of control[...]? since if you change ORM, you'd still have to change Book so that it wouldn't be class of Model
Well... yes and no. Yes, I've instantiated the model-class in the test directly, but model in this context doesn't necessarily mean instance of Eloquent\Model but more like the model in model-view-controller. Eloquent is only the ORM and has a class named Model that you inherit from, but the model-class as itself is just an entity of the business logic. It could extend Eloquent, it could extend Doctrine, or it could extend nothing at all.
In the end it's just a class that holds the data that you pull e.g. from a database, from an architecture point of view it is not aware of any ORM, it just contains data. A Book might have an author attribute, maybe even a getAuthor() method, but it doesn't really make sense for a book to have a save() or find() method. But it does if you're using Eloquent. And it's ok, because it's convenient, and in small project there's nothing wrong with accessing it directly. But it's the repository's (or the controller's) job to deal with a specific ORM, not the model's. The actual model is sort of the outcome of an ORM-interaction.
So yes, it might be a little confusing that the model seems so tightly bound to the ORM in Laravel, but, again, it's very convenient and perfectly fine for most projects. In fact, you won't even notice it unless you're using it directly in your application code (e.g. Book::where(...)->get();) and then decide to switch from Eloquent to something like Doctrine - this would obviously break your application. But if this is all encapsulated behind a repository, the rest of your application won't even notice when you switch between databases or even ORMs.
So, you're working with repositories, so only the eloquent-implementation of the repository should actually be aware that Book also extends Eloquent\Model and that it can call a save() method on it. The point is that it doesn't (=shouldn't) matter if Book extends Model or not, it should still be instantiable anywhere in your application, because within your business logic it's just a Book, i.e. a Plain Old PHP Object with some attributes and methods describing a book and not the strategies how to find or persist the object. That's what repositories are for.
But yes, the absolute clean way is to have a BookInterface and then bind it to a specific implementation. So it could all look like this:
Interfaces:
interface BookInterface
{
/**
* Get the ISBN.
*
* #return string
*/
public function getISBN();
}
interface BookRepositoryInterface()
{
/**
* Find a book by the given Id.
*
* #return null|BookInterface
*/
public function find($id);
}
Concrete implementations:
class Book extends Model implements BookInterface
{
public function getISBN()
{
return $this->isbn;
}
}
class EloquentBookRepository implements BookRepositoryInterface
{
protected $book;
public function __construct(Model $book)
{
$this->book = $book;
}
public function find($id)
{
return $this->book->find($id);
}
}
And then bind the interfaces to the desired implementations:
App::bind('BookInterface', function()
{
return new Book;
});
App::bind('BookRepositoryInterface', function()
{
return new EloquentBookRepository(new Book);
});
It doesn't matter if Book extends Model or anything else, as long as it implements the BookInterface, it is a Book. That's why I bravely instantiated a new Book in the test. Because it doesn't matter if you change the ORM, it only matters if you have several implementations of the BookInterface, but that's not very likely (sensible?), I guess. But just to play it safe, now that it's bound to the IoC-Container, you can instantiate it like this in the test:
$book = $this->app->make('BookInterface');
which will return an instance of whatever implementation of Book you're currently using.
So, for better testability
Code to interfaces rather than concrete classes
Use Laravel's IoC-Container to bind interfaces to concrete implementations (including mocks)
Use dependency injection
I hope that makes sense.
Whilst creating an OwnableBehavior I decided to use the $mapMethods property that is available. It is to map any method called isOwnedByXXX() to isOwnedBy() (The link for the documentation on this is here)
Here is my OwnableBehavior code:
class OwnableBehavior extends Model Behavior {
public $mapMethods = array('/isOwnedBy(\w+)/' => 'isOwnedBy');
public function isOwnedBy(Model $model, $type, $id, Model $userModel, $userId) {
// Method is currently empty
}
}
Here is the TestCase code:
class OwnableBehaviorTest extends CakeTestCase {
public function testIsOwned() {
$TestModel = new Avatar();
$TestModel->Behaviors->attach('Ownable');
$result = $TestModel->Behaviors->Ownable->isOwnedByUser(
$TestModel, 1, new User(), 1);
$this->assertTrue($result);
}
}
When I run the test I get this error:
Call to undefined method OwnableBehavior::isOwnedByUser()
If I change the method call to isOwnedBy($TestModel, 'user', 1, new User(), 1); this works, so it looks like for some reason the mapped methods aren't working during the unit test. I have tested the mapped methods in a controller and I get no errors.
I wondered if it was down to how I was loading the behaviour into the model. I couldn't find any documentation in the cookbook on how to properly test Behaviours like there is with Components, Helpers, etc... So I just used the same techniques that the Core Behaviour tests use (Found in Cake/Test/Case/Model/Behavior/).
I did think maybe it could have been down to the fact that I am overwriting the ModelBehavior::setup() method, but I tried adding parent::setup($model, $settings) at the start of the setup method and I still get the same error. I am not overwriting any of the other ModelBehavior methods.
I guess I could just use the OwnableBehavior::isOwnedBy() method, but I'd quite like to know if I could get the mapped methods to work during a unit test.
The solution I have found is replacing this line:
$result = $TestModel->Behaviors->Ownable->isOwnedByUser(...);
with:
$result = $TestModel->isOwnedByUser(...);
So it's just a case of using it more like you would in the application, calling the behaviour method directly from the model. I don't know if this ruins the idea of a unit test and makes it more into integration testing though.
I've a Grails (3+) service where a domain object is retrieved from the DB, modified , and then updated in the DB.
class MyService {
def modifyObject(String uuid) {
def md = MyDomain.findByUuid(uuid)
md.someField = true
if (!md.save()){
throw new MyException()
}
}
}
Now I need to test this service method with a negative test, to be sure that the exception is thrown. But I can't figure out how to force a failing save in the service method.
#TestFor(MyService)
#Mock(MyDomain)
class MyServiceSpec extends Specification {
void "test An exceptionl situation was found"() {
given: "An uuid"
def md = new MyDomain(uuid: "123")
md.save(failOnError: true)
when: "service is called"
service.modifyObject("123")
then: "An exception is thrown"
thrown MyException
}
}
Obviously, re-defining the service method in a more functional way (the object is passed directly to the method, modified and returned without save .e.g MyDomain modifyObject(MyDomain md)) will be a good solution since I could create an invalid ad hoc object outside or even invalidate it after the method execution.
But the question is: "is there a way to test the service code as is?"
Assuming that you really do want to throw an exception and not just handle validation errors, then sure. You'll want to utilize Spock's support for interaction based testing and leverage static method stubs. See similar question, Unit test grails with domain objects using GORM functions.
You need some way to isolate the service method and stub out the GORM functionality. This can be tricky with static methods, but can be accomplished with a global GroovyMock or GroovySpy. In essence, you're replacing all instances/references to MyDomain for the duration of the method (though the GroovySpy will fall back on the actual domain class unless an interaction matches).
With the Mock/Spy in place, you can specify the interactions you expect to occur and specify what those interactions should return. In this case, we expect the findByUuid to be invoked with an argument of "123" once, and we return a mock MyDomain object. That mock object then has it's save() method invoked once where we return null, i.e. the save failed.
void "test An exceptional situation was found"() {
setup:
GroovySpy(MyDomain, global: true)
def mockDomain = Mock(MyDomain)
when: "service is called"
service.modifyObject("123")
then: "An exception is thrown"
1 * MyDomain.findByUuid("123") >> mockDomain
1 * mockDomain.save() >> null
thrown Exception
}
If you violate a constraint on MyDomain and the save should fail, no?
If the modifyObject is really that simple, and you can't pass in a bogus value to force a constraint violation, maybe you need to metaClass the MyDomain.save and force a failure there.