symfony: Cannot get service in tests

Symfony version: 4.4.5

I’m starting a new project (using SF 4.4 because it’s LTS) and in my tests I’d like to use the new feature that enables getting services easily in tests.

However even when doing everything as described on a completely fresh project without any hacks I’m still getting this error:

Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The "MyServiceClass" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.

The exception stack trace contains vendor/symfony/framework-bundle/Test/TestContainer.php:106 which proves that the “special container” as described in the post is indeed used. But I still can’t get the service.

What else do I need to enable to use this feature?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 20 (14 by maintainers)

Commits related to this issue

Most upvoted comments

Personally speaking, I think integration tests should only be testing public services anyway.

@enumag I’m going totally off-topic from your original bug report, but perhaps it might help solve it in a different way. I think most of us have run into this problem in our own projects, and in my own work, I approach this by using interfaces on my repositories.

// UserRepository.php
interface UserRepository
{
    public function findOneByID($id): ?User;
}

// DoctrineUserRepository.php
class DoctrineUserRepository implements UserRepository
{
    public function findOneById($id): ?User
    {
        // normal Doctrine code
    }
}

class InMemoryUserRepository implements UserRepository
{
    private array $users = [];

    public function findOneById($id): ?User
    {
        return $this->users[$id];
    }
}

class UserService
{
    // signature is on the interface, not the implementation
    public function(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function doSomething()
    {
        $user = $this->userRepository->findOneById(7);
    }
}

class UserServiceTest extends TestCase
{
    public function test_it_does_something()
    {
        $service = new UserService(new InMemoryUserRepository);
    }
}

These tests are quicker to run and less flaky. The only place I actually test the queries are running correctly is in full functional tests (i.e. WebTestCase tests) and Doctrine repository tests. Unit tests always use the in memory implementations.

Where I have other complex services as dependencies, I use a decorator so I can inject stub implementations in unit tests, and the real thing everywhere else.

There’s a good chance that, for some reason, you’ve already come across and rejected this pattern but I thought I’d leave it here for anyone else struggling with the problem you mentioned.

The public alias is the recommended way here.

@Taluu 🤦‍♂

From what I can see, you are trying to do here a unit test… So mock the dependencies and be done with it.

From what I see you didn’t bother to read:

The service in question has a dependency on EntityManager and it is a service responsible for fetching the correct data from the database. I need to test that the SQL queries are returning the intended results.

How would you unit test a service responsible for fetching database data? Of course I can’t mock anything. That would make the test completely pointless. I need to verify that the SQL queries work as intended and of course I can only do that when testing against a real database.

From what I can see, you are trying to do here a unit test… So mock the dependencies and be done with it.

If you have too many arguments, then it means you are breaking the single responsability principle which usually means your service does too much.

Mine too. :}

Ok, so I really think you should either use a public alias or even better, instantiate them yourself. Do not test the container instantiation, but your object (you can stlll fetch the entity manager from the container…).

You’re right, sorry, I didn’t realise these were repository services you were trying to test.

I agree with you about not extending from EntityRepository, so my repository tests typically look like this:

$doctrineUserRepository = new DoctrineUserRepository($container->get(EntityManager::class));

So when you’re trying to load the service in an integration test, the container is returning a confusing message about the service being missing. A message like “this service was removed from the container because it’s not used anywhere” would make it easier for you to debug why you’re not able to load it in the test. Did I understand correctly?

See https://github.com/symfony/symfony/issues/28528

I’m guessing the service MyServiceClass is not used anywhere in your app (yet)? So its removed when the container is compiled.