How to login a user in unit test within Symfony in an example
As explained in the Symfony testing, a unit test is a test against a single PHP class, all dependencies should not be tested within this class, so people use mock technic as much as tested services need dependencies, one of common problems is when a service depends on the connected user to do some stuff (check roles, inject user in other entity, …), the Symfony\Component\Security\Core\Security
service holds the connected user inside a Token (retrieved by calling the token_storage
service), so how to inject a user object inside a token so services can retrieve it ?
Example : In the following example, I will illustrate a Post
created by the connected User
The code is available on git :https://github.com/ybenhssaien/sf-unittest-login
- Let’s create the
Post
entity associated with theUser
as its author (the connectedUser
)
2. To ensure every created post has an author, we can either create a service with the create
method or implement a listener to the doctrine event (PrePersist) in order to inject the connected user as author (I choosed for this example a simple PostService
)
$post->setAuthor($this->security->getUser())
: Injects the connected user as the post’s author.
3. Create a simple unit test against create
method to check if the post gets inserted in the database (How to test interacting with the database) and has an author :
Note that this test fails as PostService::create()
try to inject null in Post
entity since User doesn’t exist.
Because of the optional type hint of the parameter
Post:setAuthor(?User $user)
a Doctrine exception is thrown, forcing the parameter to not accept null, will throw an Error Fatal
So how we can inject a user in the token storage to let PostService
retrieve it ?
Thanks to Symfony service decorator we can decorate the token storage 😍
4. We need now to make it possible to login a user with a given role before to launch the test, let’s make a login
and logout
methods call :
5. The login
and logout
methods are defined in SecurityTrait :
Note that the property
protected static ?User $user
is used only to store the last retrieved user from database to minimise queries (There are many ways to do it, I chose the simple one for this example)
6. To make it possible to inject a user in token storage, I’ll create(as mentioned above) a TokenStorageDecorator
and make it available as a service (thanks to autowiring)
In case the token is null, create a new one :
7. The last step, to make it work, we need to replace the default token storage by the decorated token storage only in atest
environement, I’ll create the config/service_test.yaml
file :
With
decorates
key under the service name, every call for servicesecurity.token_storage
returns theTokenStorageDecorator
instead automatically.
That’s all 😊 tests work now as expected