Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Resetting the Database Between Tests

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

One of the trickiest things about functional tests is controlling the database. In my opinion, a perfect world is one where the database is completely empty at the start of each test. That would give us a completely predictable state where we could create whatever User or CheeseListing objects we want and know exactly what's in the database.

Some people prefer to go a step further and load a predictable set of "fixtures" data before each test - like a few users and some cheese listings. That's fine, but it's not the approach I prefer. Why? Because if we can make the database empty, then each test is forced to create whatever data - like users or cheese listings - that it needs. That might sound bad at first... because... that's more work! But the end result is that each test reads like a complete story: we can see that we get a 401 status code, then we create a user, then we log in with the user we just created. A nice, complete story.

Installing Alice

So... how can we empty the database before each test? There are a few answers, but one common one you'll see in the API Platform world is called Alice. Find your terminal and install it with:

composer require "alice:^2.7" --dev

This will install hautelook/alice-bundle. What does that bundle actually do? I've talked about it a few times in the past on SymfonyCasts: it allows you to specify fixtures data via YAML. It's really fun actually and has some nice features for quickly creating a set of objects, using random data and linking objects to each other. It was the inspiration behind a fixture class that we created and used in our Symfony Doctrine tutorial. The recipe creates a fixtures/ directory for the YAML files and a new command for loading that data.

The ReloadDatabaseTrait

But... what does any of that have to do with testing? Nothing! It's just a way to load fixture data and you can use it... or not use it. But AliceBundle has an extra, unrelated, feature that helps manage your database in the test environment.

Back in our test class... once PHPStorm finishes reindexing we're going to use a new trait: use ReloadDatabaseTrait.

... lines 1 - 6
use Hautelook\AliceBundle\PhpUnit\ReloadDatabaseTrait;
... line 8
class CheeseListingResourceTest extends ApiTestCase
{
use ReloadDatabaseTrait;
... lines 12 - 39
}

That's it! Just by having this, before each test method is called, the trait will handle emptying our database tables and reloading our Alice YAML fixtures... which of course we don't have. So, it'll just empty the database.

Try it!

php bin/phpunit

We see even more deprecation warnings now - the AliceBundle has a few deprecations it needs to take care of - but it works! We can run it over and over again... it always passes because the database always starts empty. This is a huge step towards having dependable, readable tests.

Removing the Logging Output

While we're here, we're getting this odd log output at the top of our tests. I can tell that this is coming from Symfony... it's almost like each time an "error" log is triggered in our code, it's being printed here. That's... ok... but why? Symfony normally stores log files in var/logs/test.log, for the test environment.

The answer is... because we never installed a logger! Internally, Symfony ships with its own logger service so that if any other services or bundles want to log something, it's available! But that logger is super simple... on purpose: it's just meant as a fallback logger if nothing better is installed. Instead of writing to a file, it logs errors to stderr... which basically means they get printed to the screen from the command line.

Let's install a real logger:

composer require "logger:^3.4"

This installs Monolog. When it finishes... try running the tests again:

php bin/phpunit

And... no more output! Now the logs are stored in var/log/test.log. If you tail that file...

tail -f var/log/test.log

there it is!

Next, I want to make one more improvement to our test suite before we get back to talking about API Platform security. I want to create a base test class with some helper methods that will enable us to move fast and write clean code in our tests.

Leave a comment!

60
Login or Register to join the conversation
Anton B. Avatar
Anton B. Avatar Anton B. | posted 2 years ago

Sorry, but if I don't want just to hide the uncaught exceptions inside a logger?For example it could be thrown an AccessDeniedHttpException which would not be catched by API platform. What should I do in this situation?How I can handle it?

1 Reply

Hey @Anton Pool!

I’m not sure if I fully understand your question, but let me try to answer and you can tell me if I’ve misunderstood :).

So, if you throw an exception from anywhere in your app, it triggers a process in symfony. Part of that process is that the error is logged. The other part of that process all about what you should return to the user. When you’re inside ApiPlatform, it tries to format those errors as JSON.

So, you’re errors aren’t exactly hidden inside a logger. They ARE logged, but then some response is also sent to the user. If you’d like to be notified of an error (instead of it just going into a log file), you can absolutely do that with the monolog config. 500 level errors are sent to a channel in our slack, for example.

Anyways, let me know if I answered your question. And if I didn’t (which is likely), give me a bit more info and I’ll do my best :).

Cheers!

Reply
Anton B. Avatar

I meant if I do not have a logger installed, (just no need for this), how can I handle, for example, AccessDeniedException inside the terminal?In other words when I run the tests, I expect 403 status. Tests are passing but Uncaught AccessDeniedException appears in terminal. How I can avoid this?

Reply

Ah... yes! I know exactly what you're talking about.

What happens is that when you don't have a logger installed, Symfony gives you a super basic one (just so that all the core services that require a logger having something they can use and log to). Here is that Logger: https://github.com/symfony/symfony/blob/5.x/src/Symfony/Component/HttpKernel/Log/Logger.php

The easiest thing to do is install a logger in dev mode - composer require logger --dev, but if you truly don't want a logger, you can add this to your services.yaml:


services:
    # ... all the normal stuff

    logger:
        class: Psr\Log\NullLogger

If you do this, then Symfony will use your Logger... which will do nothing :).

Cheers!

Reply
Anton B. Avatar

Nope =) The idea is not to hide the Uncaught exceptions messages, but to handle them. Maybe I should create an Exception Listener and then catch any Uncaught Exception here. Cause API platform does not catches an AccessDeniedException by default. Cheers!

Reply

Hey Anton B.!

Yep! An Exception Listener will give you full control over handling any exception messages :). In fact, the reason why errors are sent to the log is thanks to one of the core listeners - https://github.com/symfony/...

> Cause API platform does not catches an AccessDeniedException by default

Does it not? I thought it did. What do you see when you get an access denied page? The big HTML page?

Cheers!

Reply
Anton B. Avatar
Anton B. Avatar Anton B. | weaverryan | posted 2 years ago | edited

Hey weaverryan !
Just to describe what I mean
This is part of my code (functional test) that extends ApiTestCase class:

`
$client = static::createClient();

$client->request('POST', self::ROUTE_PREFIX, [

'json' => [],
'headers' => ['content-type' => 'application/ld+json; charset=utf-8'],

]);
$this->assertResponseStatusCodeSame(401);
`
This code works as expected. But if I change status code to 403 - the tests would pass but in terminal appears Uncaught AccessDeniedException. So in my DI container it would be tons of error messages about it. API platform catches 401 but does not catch 403 exception. Can you help me with that?Expected behaviour is that AccessDeniedException would not be shown in terminal by default during the tests running

Reply

Hey Anton B.!

This definitely helps. But to make sure I absolutely understand what's going on, could you take a screenshot of the "Uncaught AccessDeniedException" messages that you see in the terminal while running your tests?

Because, my theory (which is only just a theory) is that everything is working just fine (e.g. the 403 response comes back as JSON and is being handled correctly), but you are seeing the logs in your terminal simply because - if you don't install MonologBundle - that default logger - logs things to the terminal (which can be quite annoying). If I'm correct, it means that (A) your app is working just fine but (B) you have a "logger" service that is being loud and annoying :). The fix would be to "composer require logger" or replace the logger with the Null logger (the services code I showed earlier).

But, I could be wrong about the situation - a screenshot should help me get the correct idea if I'm wrong :).

Cheers!

Reply
Anton B. Avatar
Anton B. Avatar Anton B. | weaverryan | posted 2 years ago | edited

Hey weaverryan !
Please review this screenshot

Reply

Hey Anton B.!

Excellent! Yep, this is the "built-in" logger just being annoying - you can see where this log is formatted - https://github.com/symfony/...

So your system is working just fine. It's just that this logger is annoying. You should install monolog or set the logger to a NullLogger. You could even set the logger to a NullLogger *just* in the test environment if you want, by putting the service config I listed earlier into services_test.yaml :).

Cheers!

Reply
Anton B. Avatar
Anton B. Avatar Anton B. | weaverryan | posted 2 years ago | edited

Hey weaverryan!
Sorry, but why 401 - Unauthorized is not showing the AccessDeniedException or 400 - Bad request is not showing BadRequest exception?Only 403 and 404 shows the exception in terminal

Reply

Hey Anton B.!

The 401 isn't shown because the exception is intercepted before the ErrorListener (which logs the error) in the ExceptionListener from the security component. Basically, that listener catches the exception, creates the 401 and sets it. Then, the ErrorListener that logs is never called.

Why aren't things like 400 exceptions logged? I'm not sure about that. I would expect those to also be logged. My guess is that API Platform is somehow treating those differently, but I can't see where exactly.

Cheers!

Reply
Ivan-P Avatar

When running my tests, with a blank SQLite DB I get a Doctrine\DBAL\Exception\TableNotFoundException because, of course, the schema hasn't been yet created. I do create it locally and it solves the problem but it will force my colleagues to do it manually when they pull my code. Should they just deal with it? Is there a way to configure the tests so they create the schema automatically? What is the best practice in this case?

Thanks!

Reply

Hey Ivan,

I think the best option is to add the creation of the database schema part of your test environment setup.

Cheers!

Reply
Ivan-P Avatar

Why doesn't it need to be taken care of in these (and many other) examples? Would I do it in the Test class setup method, in phpunit.xml.dist or somewhere else?

Thanks for the quick response!

Reply

For me, that's part of your setup process and doesn't belong to your test code. For example, let's suppose you have ElasticSearch in place in your project, and you're going to write a few tests. Would you make part of your tests setting up ElasticSearch? Most likely not because that can be a time-consuming process.

Would I do it in the Test class setup method, in phpunit.xml.dist or somewhere else?

Creating the testing database can be a manual step before running your tests but you could create a shell script if you want to automate that process (it would just run a few doctrine commands)

Cheers!

Reply
Mehul-J Avatar

php 8 does not support ReloadDatabaseTrait

Reply

Hey @Mehul-J!

Does it not? I see that the latest version of hautelook/alice-bundle - 2.11.0 (which contains ReloadDatabaseTrait) does support PHP 8.1 and higher. Are you hitting a problem when installing this bundle? Or when using this trait? Btw, the course code for this tutorial is now old - we'll update the tutorials soon for API Platform 3.

Also, in newer projects, instead of Alice, we use Foundry for data fixtures and for resetting the database between tests - https://github.com/zenstruck/foundry

Cheers!

Reply
Cameron Avatar
Cameron Avatar Cameron | posted 1 year ago

Im not getting logs in my test environment (when I goto the web profiler), I imagine this is because I'm in the test ENV. is there a way to get logger->info() stuff in the console or a via the web profiler?

I'm enjoying BDD, but without my logger, I'm really flying blind (and print "" and dump() are not ideal.) I think this would be a great addition to this course too - a video on the best way to debug and log tests?

Reply

Hey Fox C.!

Hmm. It IS true that there is monolog configuration for the "dev" environment and "prod" environment, but none for the "test" environment (I can't actually remember if that means logging is entirely off in test - but I think it does). Try duplicating the config/packages/dev/monolog.yaml file to config/packages/test/monolog.yaml and see if that helps. If it does, an even better solution (instead of duplication) is to "import" the config/packages/dev/monolog.yaml file from the test/monolog.yaml file. Here's an example from a different library about how to do that: https://github.com/symfony/recipes/blob/master/zenstruck/foundry/1.9/config/packages/test/zenstruck_foundry.yaml

I'm enjoying BDD, but without my logger, I'm really flying blind (and print "" and dump() are not ideal.) I think this would be a great addition to this course too - a video on the best way to debug and log tests?

Yea, we're overdue for updating our test courses here :). But you're absolutely right - debugging is key. For testing an API, the most important thing is that, when you have an error, you can SEE the response so that you can immediately dig in and figure out what's going on. There's actually an alternate testing library - https://github.com/zenstruck/browser - which does a good job on this. If you follow the README (including the part that updates your phpunit.xml.dist file), then on each failure, the last response contents is dumped to a file, so you can easily view it.

Also, about "dumping" data and wanting to be able see it easily, you can run the server:dump command - https://symfony.com/doc/current/components/var_dumper.html#the-dump-server.

When this is running, any dump() commands that are hit are dumped to THAT terminal so that you can quickly check them out.

I hope this helps.

Cheers!

Reply
Cameron Avatar

server:dump looks super useful thanks.

+1 for tutorial on zenstruck - it looks like this library would also alert you to errors in react / vue also - as it inputs the fields and is capable of running JS. That would be really interesting.

Reply

We're hoping to get a whole big testing tutorial out in 2022, which would almost definitely include this library :)

1 Reply
Cameron Avatar

thanks, I'll take a look.

ah yes, I remember my struggles with API testing - in particular the ->getContents() on the response will acctually throw an exception (if the status code isn't 200!) which was quite confusing. I also remember writing code to display the stack trace in the console (or alternatively: I had to "recreate" the test in postman to get the stack trace) to allow me to debug logic problems in my app - as it's obviously not in the web profiler. This was a bit time consuming to learn myself.

More training on logging / debuggging in tests would be useful if you're creating new training as I expect others would have had similar problems trying to figure out things like ->getContents().

Reply

The Hautelook/Alice bundle repository on GitHub has been made private. So its not accessible. It has affectred several pipelines including mine. Read about a workaround from one of the maintners here

Reply

Hey sridharpandu!

Ah yes - I had heard about this, but forgot that we used it in this tutorial! I'll take a look and see what adjustments or notes we need to add to guide people around this - I appreciate you pointing it out!

Thanks!

Reply

It has been resolved now. The bundle now points to the Packagist repo. A composer update should do the trick. (Conversation thread on Github)

1 Reply

Sweet! No updates needed here then - that's my favorite situation. I appreciate you following up!

1 Reply

Looks like I have hit a unusual problem. I had created entities and test cases in the past and the test cases worked as intended when run. The last few days I modified two of the entities and included additional fields. I then modified the tests to pass data for these additional fields. However when I run the tests PHPunit throws integrity constraint errors because the values for these new fields are somehow becomming NULL. Let me illustrate with a real example.


class JournalVoucher
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="integer")
     */
    private $client_AuditId;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clientvo_LedgerCode;

    /**
     * @ORM\Column(type="string", length=99)
     */
    private $clientvo_LedgerName;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clientvo_LedgerType;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clientvo_SubLedger1;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clientvo_SubLedger2;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clientvo_SubLedger3;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clientvo_SubLedger4;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clientvo_SubLedger5;

    /**
     * @ORM\Column(type="string", length=99)
     */
    private $clientvo_Narration;

    /**
     * @ORM\Column(type="float")
     */
    private $clientvo_DebitAmountLCY;

    /**
     * @ORM\Column(type="float")
     */
    private $clientvo_CreditAmountLCY;

    /**
     * @ORM\Column(type="float", nullable=true)
     */
    private $clientvo_DebitAmountFCY;

    /**
     * @ORM\Column(type="float", nullable=true)
     */
    private $clientvo_CreditAmountFCY;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clienttb_CreatedBy;

    /**
     * @ORM\Column(type="datetime")
     */
    private $clienttb_CreatedOn;

    /**
     * @ORM\Column(type="string", length=20)
     */
    private $clientvo_number;

    /**
     * @ORM\Column(type="string", length=9)
     */
    private $clientvo_leg;```


The relevant portion of the test that causes an error
    $client->request('POST',  '/api/journal_vouchers',
                     ['json' => [
                                'clientAuditId' => 2,
                                'clientvoLedgerCode' => '120',
                                'clientvoLedgerName' => 'Petty Cash',
                                'clientvoLedgerType' => 'Cash',
                                'clientvoSubLedger1' => '',
                                'clientvoSubLedger2' => '',
                                'clientvoSubLedger3' => '',
                                'clientvoSubLedger4' => '',
                                'clientvoSubLedger5' => '',
                                'clientvoNarration'  => 'Expenses for Stationery Items',
                                'clientvoDebitAmountLCY' => 0,
                                'clientvoCreditAmountLCY' => 214.00,
                                'clientvoDebitAmountFCY' => 0,
                                'clientvoCreditAmountFCY' => 0,
                                'clienttbCreatedBy' => '000000001',
                                'clienttbCreatedOn' => '2021-03-17T15:03:14.159',
                                'clientvonumber' => 'JV202021000000000001',
                                'clientvoleg' => '000000001',
                    ]]);
    $this->assertResponseStatusCodeSame(201);


When I run the test I get the following errors.

7) App\Tests\Functional\JournalVoucherTest::testCreateJournalVoucher
Failed asserting that the Response status code is 201.
HTTP/1.1 500 Internal Server Error
Cache-Control: max-age=0, must-revalidate, private
Content-Type: application/ld+json; charset=utf-8
Date: Tue, 06 Apr 2021 03:25:08 GMT
Expires: Tue, 06 Apr 2021 03:25:08 GMT
Link: <http://example.com/api/docs.jsonld&gt;; rel="http://www.w3.org/ns/hydra/core#apiDocumentation&quot;
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-Robots-Tag: noindex

{"@context":"\/api\/contexts\/Error","@type":"hydra:Error","hydra:title":"An error occurred","hydra:description":"An exception occurred while executing \u0027INSERT INTO journal_voucher (client_audit_id, clientvo_ledger_code, clientvo_ledger_name, clientvo_ledger_type, clientvo_sub_ledger1, clientvo_sub_ledger2, clientvo_sub_ledger3, clientvo_sub_ledger4, clientvo_sub_ledger5, clientvo_narration, clientvo_debit_amount_lcy, clientvo_credit_amount_lcy, clientvo_debit_amount_fcy, clientvo_credit_amount_fcy, clienttb_created_by, clienttb_created_on, <b>clientvo_number, clientvo_leg</b>) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\u0027 with params [2, \u0022120\u0022, \u0022PETTY CASH\u0022, \u0022Cash\u0022, \u0022\u0022, \u0022\u0022, \u0022\u0022, \u0022\u0022, \u0022\u0022, \u0022EXPENSES FOR STATIONERY ITEMS\u0022, 0, 214, 0, 0, \u0022000000001\u0022, \u00222021-04-06 08:55:08\u0022, <b>null, null</b>]:\n\nSQLSTATE[23000]: Integrity constraint violation: 1048 Column \u0027clientvo_number\u0027 cannot be null","trace":`

The values in the test case are for some reason not being passed correctly (see the emphasized portion). Is there something that I should have done before running the test case?

Reply

I included a dd($client) in my test case and found that though the request has all the parameters that are specified in the test the moment it gets passed on to the Symfony HTTPClient the "parameterbag" ignores the two attributes that were included when the entity was modified. I tried changing the order of the attributes but that makes no difference, it specifically excludes the attributes that were added when the entity was modified. Here is a snippet. I have highlighted some lines to pinpoint the issue.


    #internalRequest: Symfony\Component\BrowserKit\Request {#95
      #uri: "http://example.com/api/journal_vouchers"
      #method: "POST"
      #parameters: []
      #files: []
      #cookies: []
      #server: array:5 [
        "HTTP_USER_AGENT" => "Symfony BrowserKit"
        "HTTP_ACCEPT" => "application/ld+json"
        "CONTENT_TYPE" => "application/json"
        "HTTP_HOST" => "example.com"
        "HTTPS" => false
      ]
      #content: "{"clientAuditId":2,"clientvoLedgerCode":"120","clientvoLedgerName":"Petty Cash","clientvoLedgerType":"Cash","clientvoSubLedger1":"","clientvoSubLedger2":"","clientvoSubLedger3":"","clientvoSubLedger4":"","clientvoSubLedger5":"","clientvoNarration":"Expenses for Stationery Items","clientvoDebitAmountLCY":0,"clientvoCreditAmountLCY":214.0,"clientvoDebitAmountFCY":0,"clientvoCreditAmountFCY":0,<b>"clientvonumber":"JV202021000000000001","clientvoleg":"000000001"</b>,"clienttbCreatedBy":"000000001","clienttbCreatedOn":"2021-03-17T15:03:14.159"}"
    }
    #request: Symfony\Component\HttpFoundation\Request {#117
      +attributes: Symfony\Component\HttpFoundation\ParameterBag {#91
        #parameters: array:12 [
          "_route" => "api_journal_vouchers_post_collection"
          "_controller" => "api_platform.action.post_collection"
          "_format" => null
          "_stateless" => null
          "_api_resource_class" => "App\Entity\JournalVoucher"
          "_api_identifiers" => array:1 [
            0 => "id"
          ]
          "_api_has_composite_identifier" => false
          "_api_collection_operation_name" => "post"
          "_route_params" => array:6 [
            "_format" => null
            "_stateless" => null
            "_api_resource_class" => "App\Entity\JournalVoucher"
            "_api_identifiers" => array:1 [
              0 => "id"
            ]
            "_api_has_composite_identifier" => false
            "_api_collection_operation_name" => "post"
          ]
          "_firewall_context" => "security.firewall.map.context.main"
          "data" => App\Entity\JournalVoucher {#961
            -id: null
            -client_AuditId: 2
            -clientvo_LedgerCode: "120"
            -clientvo_LedgerName: "PETTY CASH"
            -clientvo_LedgerType: "Cash"
            -clientvo_SubLedger1: ""
            -clientvo_SubLedger2: ""
            -clientvo_SubLedger3: ""
            -clientvo_SubLedger4: ""
            -clientvo_SubLedger5: ""
            -clientvo_Narration: "EXPENSES FOR STATIONERY ITEMS"
            -clientvo_DebitAmountLCY: 0.0
            -clientvo_CreditAmountLCY: 214.0
            -clientvo_DebitAmountFCY: 0.0
            -clientvo_CreditAmountFCY: 0.0
            -clienttb_CreatedBy: "000000001"
            -clienttb_CreatedOn: DateTimeImmutable @1617975522 {#802
              date: 2021-04-09 19:08:42.580142 Asia/Kolkata (+05:30)
            }
<b>            -clientvo_number: null
            -clientvo_leg: null
</b>          }```

Reply

Hey sridharpandu!

Hmmm, I'm not sure what's going on. Adding new fields shouldn't cause any weird behavior like this. So here are some things I'm thinking about:

1) JUST to be save, trying clear your cache directory - run php bin/console cache:clear and php bin/console cache:clear --env=test to be safe. This should not be needed.... but let's just make sure some cache isn't out of date.

2) If you go to the documentation frontend (the Swagger docs), can you see the "clientvonumber" and "clientvoleg" field there? One thing I'm noticing is that all your existing fields are always "somelowercasetext_WordThatStartsWithUppercase". But in this situation, the "number" and "leg" part are lowercase. That could be causing API Platform to think that these field should literally be sent as "clientvo_number" in the JSON instead of "clientvonumber".

Let me know if this helps :).

Cheers!

1 Reply

Managed to resolve this issue. You were spot on when you suspected the naming conventions of the attributes in the class. I had some in camelCase and the new ones in lowercase. When I examed the entity class getter and setter methods the property names were in lowercase.

`$this->clientexecutives_propertyname; //in getter method

$this->clientexecutives_propertyname = $clientexecutives_propertyname;` // in setter method

When I changed the property names in the entity class and in the getter and setter methods to camelCase the problem disappeared.

`$this->clientexecutives_Propertyname; //in getter method

$this->clientexecutives_Propertyname = $clientexecutives_Propertyname; //in setter method
`

P.S I have 99 tests and 789 assertions ! It would have been easier to look for a needle in a haystack!

2 Reply

I have the following test in my test folder

class FinancialStatementTypeTest extends ApiTestcase
{
    use ReloadDatabaseTrait;

    public function testCreateFinancialStatementType()
    {
        $client = self::createClient();    
    
        $client->request('POST', '/api/financial_statement_types', [
            'headers' => ['Content-Type' => 'application/json'],
            'json' => [],
        ]);
        $this->assertResponseStatusCodeSame(500);
        $em = self::$container->get('doctrine')->getManager();

        $financialstatementtype = new FinancialStatementType();
        $financialstatementtype->setFstypeId('2');
        $financialstatementtype->setFstypeShortCode('IS');
        $financialstatementtype->setFstypeName('INCOME STATEMENT');
        $financialstatementtype->setFstypeCreatedBy('000000001');
        
        $now = new DateTime();
        $now->format('Y-m-d H:i:s');
        $financialstatementtype->setFstypeCreatedOn($now);

        $em->persist($financialstatementtype);
        $em->flush();
    }

When I run the test I get the following error

There was 1 error:

1) App\Tests\Functional\FinancialStatementTypeTest::testCreateFinancialStatementType
Doctrine\ORM\ORMException: The EntityManager is closed.

/home/sridhar/Dropbox/Projects/ab-compliance-ap/vendor/doctrine/orm/lib/Doctrine/ORM/ORMException.php:146
/home/sridhar/Dropbox/Projects/ab-compliance-ap/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:790
/home/sridhar/Dropbox/Projects/ab-compliance-ap/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:612
/home/sridhar/Dropbox/Projects/ab-compliance-ap/var/cache/test/ContainerXHklPdZ/EntityManager_9a5be93.php:171
/home/sridhar/Dropbox/Projects/ab-compliance-ap/tests/functional/FinancialStatementTypeTest.php:41

ERRORS!
Tests: 1, Assertions: 1, Errors: 1.

I suspect that the date field is causing the exception. Any suggestions?

Reply

Hey sridharpandu!

This error - the entity manager being closed - is something that happens after some error has happened in the database and then you try to do something else in the database later. I believe that the original database error is probably happening inside your API request. Then you're seeing the entity manager closed error later when you have the $em->flush() later.

And, indeed, i see that you're asserting that your API request has a 500 status code. Are you expecting that the API call should fail? If so, why? I can give you a solution to "reset" the entity manager to avoid the error, but I think that's working around the issue (which is that we don't want our API endpoint to have any database errors!).

Let me know about why you're expecting your API call to return a 500 error :).

Cheers!

1 Reply

Thanks. I typed out the test case while I was watching the tutorial where the JSON payload was empty in the POST request. However I had a NOT NULL constraint on my columns and this was causing the 500 internal server error. I wanted to get past this so I checked for HTTP status 500 but then the rest of the test cases I shouldnt have assigned data to the variable and persisted them in the DB as in the tutorial, what I did was incorrect, I couldn't understand why it was being done this way in the tutorial, I was wondering why would someone want to trigger a POST and at the same time persist data directly into the DB by using doctrine, it took me some time to understand that this was done only for the initial user record to get pass authentication. I am good now. Just that i still get the 500 HTTP error due to the constraint even after passing the JSON.

` $client->request('POST', '/api/financial_statement_types', [

        'headers' => ['Content-Type' => 'application/json'],
        'json' => ['{
                       "fstype_Id":"2",
                       "fstype_ShortCode":"IS",
                       "fstype_Name":"INCOME STATEMENT",
                       "fstype_CreatedBy":"000000001",
                       "fstype_CreatedOn":"2021-02-16T10:14:14.159Z"
                   }'],
    ]);
    $this->assertResponseStatusCodeSame(200);`
Reply

Hey sridharpandu!

Ah! I understand - well I'm glad it all makes sense now. If you need more clarification on anything, let us know :).

Cheers!

1 Reply

The format for specifying the request in Symfony 5.2 is not very explicit in the documentation. Did a lot of trial to end up with the "Unsupported option "Content-Type" passed to ..." error. Phew!

Reply

The HTTP status code should be 201 and not 200

Reply

The 201 status code is returned when the request was processed successfully AND resource was created. If you created a resource you should return that status code, otherwise, return 200

Cheers!

1 Reply
Gediminas N. Avatar
Gediminas N. Avatar Gediminas N. | posted 2 years ago

Okay, so I have this code : https://imgur.com/hB5PYVJ

How should I know what status code should I get doing the $client->getResponse()->getStatusCode()?
I know that it depends on what I am doing and when, but how to debug it myself, what should happen (what status code ill get) if I do this or that? Is it all from common sense? Could I somehow find it by not having a frontend and checking from the profiler scenarios?

I need this because I am learning this course to land a back-end job, and I don't really specialize in vue.js at all and there was no tutorial how to make working frontend login authentication system without vue.

Reply

Hey Gediminas N.

The status code that your API must return depends on the action of the endpoint. Here you can check all the possible status codes that you can return and when to https://developer.mozilla.o...

You can use tools like Postman to debug your API endpoints, or create functional tests like we show in this course (by using the HttpClient object of Symfony and executing requests)

Good luck landing that job. Cheers!

Reply
David R. Avatar
David R. Avatar David R. | posted 3 years ago | edited

Hi, I am having a strange behaviour with RefreshDatabaseTrait.

My entities seems to be removed after I am accessing them when using phpunit, this is my test code having added the use RefreshDatabaseTrait in the class:

`$artist = $this->createArtist('Test artist');

$client->request('GET', '/api/artists/'.$artist->getId());
$this->assertResponseIsSuccessful();

$client->request('GET', '/api/artists/'.$artist->getId());
$this->assertResponseIsSuccessful();`

As you can see "nothing changes" in the two requests, byt the first assert is successfull and the second assert fails and I receive the following error:

1) App\Tests\Functional\Api\ArtistFollowersTest::testFollowArtist<br />Failed asserting that the Response is successful.<br />HTTP/1.1 404 Not Found

If I remove the use RefreshDatabaseTrait then the test goes ok. It is like after the request the database is erased.

Also I can't login, as after each request the user is removed and I can't call any api operation that requires the user as the user no longer exists :S

Reply
David R. Avatar
David R. Avatar David R. | David R. | posted 3 years ago | edited

I've found the solution. Each request forces the kernel to shutdown (https://github.com/symfony/framework-bundle/blob/4.4/Client.php#L119), this includes wiping the database when using RefreshDatabaseTrait.

I added after creating the client the following line:

` $client = self::createClient();

$client->disableReboot();`

And now it works, but I think that the database shouldn't be deleting on each request of the client...

Reply

Hey David R.

I'm glad to hear that you could fix your problem. I also think that's a weird behavior. Have you checked if there is an issue on the libraries repository?

Reply
David R. Avatar

I've been dealing with this all the day but I haven't found anything in any repo.

Reply

Yo David R.!

Good find with the kernel shutdown - I was thinking the exact same thing. To be honest, we use ReloadDatabaseTrait in this tutorial, which works slightly differently - it loads fixtures on boot, but does nothing on shutdown. It's totally possible - as crazy as it sounds - that RefreshDatabaseTrait isn't working for anyone correctly. Tbh, I like the ReloadDatabaseTrait because it works well for us, but I (in general) am a bit skeptic of the Alice library - I don't always like the way it works. From what I can see (and as you experienced), RefreshDatabaseTrait ::ensureKernelShutdown will be called before every request (except for the first)... which means your data would be wiped out. I would steer clear of this trait :/.

If you want similar functionality, try out https://github.com/dmaicher/doctrine-test-bundle - all of this stuff contains some magic, but that's a solid bundle.

Cheers!

Reply
David R. Avatar

Why on earth I've used RefreshDatabaseTrait instead of ReloadDatabaseTrait? these are some dev misteries that we will never know ;)

I've changed RefreshDatabaseTrait to ReloadDatabaseTrait and everything works without needing the $client->disableReboot()

I don't know how I ended using RefreshDatabaseTrait, but thanks to it, we know something new today. Sorry for the inconvenience.

Reply

I would blame IDE auto-completion if I were you ;)

Reply

For some reason I ran into an error with requirements unmet while trying to install alice.

`Your requirements could not be resolved to an installable set of packages.

Problem 1

- Conclusion: don't install hautelook/alice-bundle 2.7.2
- Conclusion: don't install hautelook/alice-bundle v2.7.1
- Conclusion: remove doctrine/persistence 1.1.1
- Installation request for hautelook/alice-bundle ^2.7 -> satisfiable by hautelook/alice-bundle[2.7.2, v2.7.0, v2.7.1].
- Conclusion: don't install doctrine/persistence 1.1.1
- hautelook/alice-bundle v2.7.0 requires doctrine/persistence ^1.3.4 -> satisfiable by doctrine/persistence[1.3.4, 1.3.5, 1.3.6, 1.3.7].
- Can only install one of: doctrine/persistence[1.3.4, 1.1.1].
- Can only install one of: doctrine/persistence[1.3.5, 1.1.1].
- Can only install one of: doctrine/persistence[1.3.6, 1.1.1].
- Can only install one of: doctrine/persistence[1.3.7, 1.1.1].
- Installation request for doctrine/persistence (locked at 1.1.1) -> satisfiable by doctrine/persistence[1.1.1].

Installation failed, reverting ./composer.json to its original content.
`

Simply running composer update doctrine/persistence and then composer require alice --dev fixed the issue.

Reply

Hey julien_bonnier

We are sorry that you got this issue, Thanks for sharing you solution! BTW we updated base code of Api platform tutorial so it should be issue free now )

Cheers!

Reply

Is there a different way to reload the db? AliceBundle does not support symfony 5.

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works great for Symfony 5 and API Platform 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3, <8.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.5
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.13.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.6
        "nesbot/carbon": "^2.17", // 2.21.3
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.3.*", // v4.3.2
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/expression-language": "4.3.*", // v4.3.2
        "symfony/flex": "^1.1", // v1.18.7
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/http-client": "4.3.*", // v4.3.3
        "symfony/monolog-bundle": "^3.4", // v3.4.0
        "symfony/security-bundle": "4.3.*", // v4.3.2
        "symfony/twig-bundle": "4.3.*", // v4.3.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.6", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "hautelook/alice-bundle": "^2.5", // 2.7.3
        "symfony/browser-kit": "4.3.*", // v4.3.3
        "symfony/css-selector": "4.3.*", // v4.3.3
        "symfony/maker-bundle": "^1.11", // v1.12.0
        "symfony/phpunit-bridge": "^4.3", // v4.3.3
        "symfony/stopwatch": "4.3.*", // v4.3.2
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.2
    }
}
userVoice