How to test create model service? - php

I decided to create CreateClassroomService to separte logic in my controller method.
class CreateClassroomService extends Service
{
public function create(string $name, User $user): ?Classroom
{
$this->checkName($name);
$classroom = new Classroom();
$created = $classroom->setName($name)
->associateUser($user)
->save();
return $created ? $classroom : null;
}
private function checkName(string $name): void
{
if (empty($name)) {
throw new InvalidArgumentException();
}
}
}
I am trying to test this service as part of learning unit testing, but I don't know how. I don't know how to mock the classroom object to control what the method should return. Does this mean that creating this service was not a good idea because I am not able to test it? Should I build this service differently? Unless the service can be tested, but I don't know how... What should I check in assertion?
This is my test but it is not good because I am not able to force what should be returned.
public function testGivenCreateCorrectDataClassroomWillBeCreated(): void
{
$name = 'Test classroom';
$user = Mockery::mock(User::class);
$result = $this->service->create($name, $user);
$this->assertTrue($result);
}

For something like this, you could simply assert that the ClassRoom has in fact been created. Docs
As per the docs, update your test class so that it's using the RefreshDatabase trait e.g.:
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
Laravel has Model Factories to make creating models with dummy data very quick and easy. There should already be a UserFactory created for you (you may need to update it if you've updated the default users migration).
public function testGivenCreateCorrectDataClassroomWillBeCreated(): void
{
$name = 'Test classroom';
$user = User::factory()->create();
$result = $this->service->create($name, $user);
$this->assertInstanceOf(ClassRoom::class, $result);
$this->assertDatabaseHas('class_rooms', ['name' => $name]);
}
Don't forget to import the User and ClassRoom models in to your test class.

Not sure how mocking works in Mockery, but lets review what you are doing: your create function that you are trying to test creates a new instance of a Classroom, and since it instantiates the object inline you have no control over it.
From the code you have written, I am assuming that you build a classroom object and return, never using the same creator instance again, so what you can do is add a constructor to CreateClassroomService through which you inject a Classroom object. If you are using the same create service to create multiple classroom objects at a time, you will also need to make sure that you somehow reset the classroom instance to its default state inbetween creation(s), or you inject a fresh & new classroom object before invoking create again. This wholly depends on what the classroom does though.
The classroom object then can be mocked through unit testing -- you inject the mock instance and youre good to go. You actually are already injecting User object, so you're already on the right lines!
class CreateClassroomService extends Service {
private ?Classroom $classroom;
public function __construct(Classroom $classroom) {
$this->classroom = $classroom;
}
...
}
Now you just need to mock your classroom object in your test, inject the mock into your service when instantiating the object, and off you go. :)
Btw I would also say you may want to consider do some more abstraction on your service in the form of interfaces or review your parent class to make it more unit testable in general. Generally speaking for effective unit testing you want to avoid static methods and new keywords; where you can absolutely not avoid it, one approach might be to wrap just that one line of code in an encapsulated method, so you can mock the method instead to return you mock data / mock instances instead (but generally if you have to do this it should be an obvious sign to alert you to having sub-par architecture).

Related

PHPUnit how to test a Repository?

I have a group of Repository classes I'd like to test. All Eloquent methods are called in these classes, however I am unsure on how to proceed. The only thing that needs to be tested, is that the methods return the right data type. I want to be sure that getByEmail($email) for example actually returns a User object, but I don't care about the implementation details.
Every example I see tells you to inject the model in to the Repository class and mock it, then you can proceed by having that Mock expect very specific method calls. Is this the correct approach? Because it seems to me that would be forcing the developer to use very specific methods when building the Eloquent query.
Here's an example:
class UserRepository {
public function __construct(User user) { ... }
public function getByEmail(string $email) : ?User {
$this->user->where('email', $email)->first();
}
}
For the test with Mock to work I have to do:
class myTest {
public function testMethodReturnsUserObject() {
$user = Mockery::mock(User::class);
$repo = new UserRepository($user);
// now, if a developer changes the eloquent method calls, this will fail.
$user->shouldReceive('where->first')->andReturn($user);
$this->assertEquals($user, $repo->getByEmail('joe.bloggs#acme.com'));
}
}
Any ideas what I should do to test that my method does indeed return a User object, without forcing the developer to use exactly one where() method, and one first() method in this example. What if the developer needs to instead make it ->select()->where()->where()->firstOrFail() . Now the unit test will break, even though it really shouldn't care about this, and only that the method returns a User.
Thanks!
Skip unit testing this, and focus on integration tests with the database. Since that's your primary functionality you want to assert: "Does this repository return the record(s) I expect?"
public function testMethodReturnsUserObject(): void
{
// Note: old factory() syntax; Laravel 8 is different
$expectedUser = factory(User::class)->create([
'id' => 1,
'email' => 'user#example.com',
]);
$repo = new UserRepository(new User());
$actualUser = $repo->getByEmail('user#example.com');
self::assertNotEmpty($actualUser);
self::assertSame(1, $actualUser->id);
self::assertNull($repo->getByEmail('notfound#example.com'));
}
Lots of great docs to assist in database testing.

PHP Unit testing dispatched events

How does one test that events were dispatched during a function call?
public function updateUser() {
//Do some update stuff
$event = new UserUpdated($user);
$event->attach([
new SendEmailAddressChangeEmail($emailAddress),
new SendEmailAddressChangeEmail($oldEmailAddress),
]);
$event->dispatch();
}
Aside from setting up an email address and seeing if an email is sent, how can I check (using PHP Unit) that the dispatcher is actually dispatching these events? I am assuming that I need to create a mock of some sort, but I am uncertain how to create a mock for a completely unrelated bit of code.
UserUpdated Event code:
class UserUpdated extends BaseEvent
{
public $user;
public function __construct(User $user) {
$this->user = $user;
}
}
and the related SendEmailAddressChanged Handler code:
class SendEmailAddressChangeEmail implements Contracts\HandlerInterface
{
protected $emailAddress;
public function __construct($emailAddress) {
$this->emailAddress = $emailAddress;
}
public function handle($event) {
EmailUtils::sendEmailAddressChangeEmail($this->emailAddress, $event->user->userName, $event->user->userID);
}
}
The updateUser() method you've got does two things in one that especially does not work well with (unit) testing:
business logic
object creation
From your own sense of things I assume this is also what made you ask this question. Often code that is not straight forward to test also is a good canary for design issues, so it is normally best to tackle w/ it.
These two points (1. and 2.) are an over-simplification of what is borrowed from the "Two piles" outlined by Misko Hevery in far more detail in his Clean Code Talks:
For example in "The Clean Code Talks -- Inheritance, Polymorphism, & Testing" from Nov 2008 - https://youtu.be/4F72VULWFvc?t=1328 ("Two Piles" # 22:08)
One solution to make this code more test-able is the use of dependency injection. That is one factory (method) for the user-event and one factory (method) for the object updateUser() is a method of. That concrete type then can make use of the factory object it gets injected to obtain the even object.
In short: If that update-user object needs a user-updated-event object it needs to ask for it in it's constructor.
As you sometimes don't want to create that user-updated-event object beforehand, the alternative is inject an object that knows how to create that user-updated-event object, these kind of objects are called factories.
The test then can inject a factory that presents an event mock object with the expectation that it is dispatched.
A good dispatch library btw. does already provide ready-made mocks for tests but that is out of the scope of Phpunit.
If you don't know yet about the mock functionality of Phpunit, please checkout the product's documentation for it:
Phpunit 7.1 Docs ยป 9. Test Doubles

How to test factory classes?

Given this class:
class MyBuilder {
public function build($param1, $param2) {
// build dependencies ...
return new MyClass($dep1, $dep2, $dep3);
}
}
How can I unit test this class?
Unit-testing it means I want to test its behavior, so I want to test it builds my object with the correct dependencies. However, the new instruction is hardcoded and I can't mock it.
For now, I've added the name of the class as a parameter (so I can provide the class name of a mock class), but it's ugly:
class MyBuilder {
public function build($classname, $param1, $param2) {
// build dependencies ...
return new $classname($dep1, $dep2, $dep3);
}
}
Is there a clean solution or design pattern to make my factories testable?
Factories are inherently testable, you are just trying to get too tight of control over the implementation.
You would check that you get an instance of your class via $this->assertInstanceOf(). Then with the resulting object, you would make sure that properties are set properly. For this you could use any public accessor methods or use $this->assertAttribute* methods that are available in PHPUnit.
http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertEquals
Many of the common assertions also have the ability to check attributes for protected and private properties.
I wouldn't specify the classname in your parameter list, as your usage is that the factory will only return one type and it is only the dependencies that are changed. Making it return a mock object type is unnecessary and makes your test more complicated.
The test would end up looking like this:
public function testBuild() {
$factory = new MyBuilder();
//I would likely put the following into a data provider
$param1 = 'foo';
$param2 = 'bar';
$depen1 = 'boo';
$depen2 = 'baz';
$depen3 = 'boz';
$object = $factory->build($param1, $param2);
$this->assertInstanceOf('MyClass', $object);
//Check the object definition
//This would change depending on your actual implementation of your class
$this->assertAttributeEquals($depen1, 'attr1', $object);
$this->assertAttributeEquals($depen2, 'attr2', $object);
$this->assertAttributeEquals($depen3, 'attr3', $object);
}
You are now making sure that your factory returns a proper object. First by making sure that it is of the proper type. Then by making sure that it was initialized properly.
You are depending upon the existence of MyClass for the test to pass but that is not a bad thing. Your factory is intended to created MyClass objects so if that class is undefined then your test should definitely fail.
Having failing tests while your developing is also not a bad thing.
So what do you want to test?
so I want to test it builds my object with the correct dependencies.
I do see a problem with this. It's either possible that you can create an object with incorrect dependencies (which should not be the case in the first place or tested in other tests, not with the factory) or you want to test a detail of the factory that you should not test at all.
Otherwise - if it's not mocking the factory what you're looking for - I see no reason why a simple
$actual = $subject->build($param1, $param2);
$this->assertInstanceOf('MyClass', $actual);
would not make it. It tests the behavior of the factory build method, that it returns the correct type.
See as well Open-Close-Principle
For tests, you can just create your MockBuilder which extends from your Builder:
class MyMockBuilder extends MyBuilder {
public function build($param1, $param2) {
// build dependencies ...
return new MyMockClass($dep1, $dep2, $dep3);
}
}
Making the classname a parameter 1:1 seems not practical to me, because it turns the factory over into something different. The creating is a detail of the factory, nothing you externalize. So it should be encapsulated. Hence the MockBuilder for tests. You switch the Factory.
As I see it, you ned to verify two things for that builder:
the correct instance is returned
values, that are injected are the right ones.
Checking instance is the easy part. Verifying values needs a bit of trickery.
The simples way to do this would be altering the autoloader. You need to make sure that when MyClass is requested for autoloader to fetch, instead of /src/app/myclass.php file it loads /test/app/myclass.php, which actually contains a "transparent" mock (where you with simple getters can verify the values).
bad idea
Update:
Also, if you do not want to mess with autoloader, you can just at th top of your myBuilderTest.php file include the mock class file, which contains definition for MyClass.
... this actually seems like a cleaner way.
namespace Foo\Bar;
use PHPUnit_Framework_TestCase;
require TEST_ROOT . '/mocks/myclass.php'
class MyBuilderTest extends PHPUnit_Framework_TestCase
{
public function MyBuilder_verify_injected_params_test()
{
$target = new MyBuilder;
$instance = $target->build('a', 'b');
$this->assertEquals('a', $instance->getFirstConstructorParam();
}
}

Making the connection between OOP theory and practice with a database driven app example

I'm new to OOP and thought I'd give Silex a try on a small app I'm attempting. I'm looking for some advice as to whether my design falls in line with good object oriented principles.
I have a User object which is basically just a bunch of properties, getters and setters. I then have a UserService object which will contain the logic for authenticating users, getting a user from the database, setting or updating user information, etc. I also have a UserServiceProvder class which is there to provide an instance of the UserService class to the app (which seems to be the best way to create a reusable chunk of code in Silex).
The question I have now is this: I am using the Doctrine DBAL that ships with Silex and when I instantiate the UserService class, I'm tempted to pass in a reference to the Doctrine object and then hard code calls to that object into methods of the UserService class.
For instance, to return a User from the database by id, I might create a method called getUserById($id) and then hardcode a Doctrine prepared statement into that method to select that user from the database and then return a User object.
Would it be better for me to create a whole other service that is just a further abstraction of the Doctrine DBAL and pass that to UserService when I instantiate it? That way, I could hard code the Doctrine prepared statements into that class, leaving my UserService class more encapsulated and reusable in case I decide to move away from Doctrine in the future.
I guess what I'm having a hard time with is realizing if there is a such a thing as overkill in OOP. It seems to me like the second method is much more reusable, but is it necessary or wise?
Moving the Database access to a separate class will bring you a couple of advantages. First of all, if you keep the database access apart from the rest of your logic you can replace the implementation of your database access more easy. If for a reason you want to drop the Doctrine DBAL you'll be happy that all the code is just referencing some interface to a repository instead of directly querying a database.
A second great advantage is that you can test your application logic in separation of your database access logic. If you inject a Repository for users inside your UserService you can Mock this in your tests and be sure they only fail if something is wrong with the actual application logic.
A small example of what you could do
The interface is convenient for reference throughout your codebase. No code references the implementation, only the interface. That way you can easily replace the implementation of the interface without touching all the places it is used:
interface IUserRepository
{
/**
* #return User
*/
public function getUserById($userId);
}
Of course you do need an implementation of said interface. This is what you inject into your UserService. This is what you one day might replace with another implementation of the interface:
class DoctrineDBALUserRepository implements IUserRepository
{
/**
* #return User
*/
public function getUserById($userId)
{
//implementation specific for Doctrine DBAL
}
}
The UserService only knows about the interface and can use it freely. To avoid having to inject the UserRepository in a lot of places in your code you could create a convenience build method. Notice the constructor that references the interface and the build method that injects an implementation of that interface:
class UserService
{
private $UserRepository;
public static build()
{
return new UserService(new DoctrineDBALUserRepository());
}
public function __construct(IUserRepository $UserRepository)
{
$this->UserRepository = $UserRepository;
}
public function getUserById($userId)
{
if ($User = $this->UserRepository->getUserById($userId) {
return $User;
}
throw new RuntimeException('O noes, we messed up');
}
With this in place you can write tests for the business logic (e.g. throw an exception if saving fails):
public function UserServiceTest extends PHPUnit_Framework_TestCase
{
public function testGetUserById_whenRetrievingFails_shouldThrowAnException()
{
$RepositoryStub = $this->getMock('IUserRepository');
$RepositoryStub->expects($this->any())->method('getUserById')->will($this->returnValue(false);
$UserService = new UserService($RepositoryStub);
$this->setExpectedException('RuntimeException');
$UserService->getUserById(1);
}
}
I can imagine you're not familiar with the last bit of code if you're not into unit-testing yet. I hope you are and if not urge you to read up on that as well :D I figured it was good for the completeness of the answer to include it no matter what.

functional testing a service

I am having trouble testing some simple services that I have in my application. This is my service method:
public function createNewCourse($details = array())
{
$course = new Course($details);
$this->persistenceManager->save($course);
}
Basically I am sending an array to this service , create the $course object which sets the properties of the object. After that I call the persistenceManager which basically has a save method which inserts the object in the database. Anyway any tips of how to test this method without actually testing the persistence, because that will be another test.
If persistanceManager is injected to your object as a dependancy then you can create a mock object to represent it in your tests.
If it is not, then you will have great difficulty unit testing and you should refactor your code to use dependancy injection.
Don't worry, dependancy injection is just a fancy phrase for a pretty simple concept.
You are going to have similar problems unit testing the Course object.
Use a stub persistenceManager and provide it to the class that contains the createNewCourse method. The stub should look like this:
class StubPersistenceManager
{
private $course;
public function save($course)
{
$this->course = $course;
}
public function getCourse()
{
return $this->course;
}
}
Ensure you inject an instance of this stub as the $this->persistenceManager in your class that contains the createNewCourse method.
In the test you call the createNewCourse method and can subsequently fetch the course instance provided to the persistence manager by using the getCourse function of the stub.

Categories