laracasts / Behat-Laravel-Extension

Laravel extension for Behat functional testing.
MIT License
260 stars 73 forks source link

Database transactions #8

Open leitom opened 9 years ago

leitom commented 9 years ago

I looks like database transactions don't work when using mysql. I dont know if this is because of laravel 5 latest changes or not.

cve commented 9 years ago

I also see that in my local environment with mysql the db transactions not work... :-/

doug7410 commented 9 years ago

I'm finding that when I run test without javascript , my test sqlite database gets used, and transactions work. When I run a test with javascript, the development database gets used and the transactions dont work. It seems like when running test with javascript the .env file is used instead of the .env.behat file. Here is my behat.yml

default:
    extensions:
        Laracasts\Behat:
            env_path: .env.behat
        Behat\MinkExtension:
            default_session: laravel
            laravel: ~
            base_url: http://aws-transcriptions.app/
            javascript_session: selenium2
            browser_name: firefox
            selenium2: ~

I wonder if this is related.

huuuk commented 9 years ago

Has anyone solved this problem?

koenvu commented 9 years ago

Perhaps the problem is that when using the Laravel session, it emulates requests without querying the actual webserver, and keeps the database transaction intact. When using javascript (using selenium), it goes to the server (that is, the server running a development, not behat environment) and does an actual web request, causing non-committed database transactions to be missing / rolled back in subsequent requests in the same test?

I'm having a similar problem with database transactions where my Background section creates a row for my Scenario's to use, but the row is missing in the tests. When I comment out the use DatabaseTransactions part of my FeatureContext, naturally the record will persist and the test succeeds. But then I would have to clean the database again manually somehow.

Does this explain the problems you guys are having? And can anyone offer a better workflow for doing these kinds of multi-request javascript tests with functional database rollbacks?

artaaa commented 9 years ago

We were experiencing similar issues as described by @koenvu when running tests with javascript (zombie-mink-driver). For now we managed to work around this by setting transaction isolation level for our MySQL database, before running the suite:

// FeatureContext.php

/**
 * @beforeSuite
 */
public static function setTransactionIsolationLevel()
{
    DB::statement('SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
}

/**
 * @afterSuite
 */
public static function resetTransactionIsolationLevel()
{
    DB::statement('SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ');
} 
pmartelletti commented 9 years ago

yep, this is definetly an issue.

@artaaa you use before suite because your test requires it, right? But If I need the database to be clean on every test, it would work the same?

@JeffreyWay any plans to work on this?

andizzle commented 8 years ago

By any chance you are making a call to a url? e.g http://localhost:8888?

I'm adding Illuminate\Foundation\Testing\ApplicationTrait to FeatureConext and making calls like normal laravel test: $this->call($httpMethod, $this->resource, json_decode($payload->getRaw(), true), [], [], $this->header);

This way the transaction and rollback happens as expected.

You need to make sure that the request you are making does not start a new thread/db connection, or the transaction won't work.

alnutile commented 8 years ago

I will try and recreate this issue in a week or so when i get back from holiday but just want to rekindle these older issues to see if they kept you from sticking with behat or if there is a solution you would like to share.

As a note @koenvu noted when using @javascript it will go through Selenium and therefore the .env file of your app is the file setting up the databasse to be used eg "DB_DATABASE=foo" but then .env.behat might be setup for "test" so them behat is talking to the wrong database. For us we set .env.behat to the same database the dev environment. Not ideal but works.

Our CI has no problems with this since the app and behat are set to use the test database. But locally this can be tricky. This even gets harder when we want to use these behat tests to set states on remote sites of ours to do post deployment testing. We have gone through the trouble here of including a route file, controller and behat user we can then log in as, on non Production sites, set state using '$this->visit('someUrlToSetStateForThisScenario')' and then cleanup state when done.

Overall though this works BUT I need to dig in more to the transaction side of things to see if that is working as well.

praditha commented 8 years ago

@andizzle I try to use your solution, but currently I'm using Laravel 5.2 which doesn't have Illuminate\Foundation\Testing\ApplicationTrait to use the call() function. Any idea for this..?

andizzle commented 8 years ago

@praditha It maybe even easier to do this in 5.2. There's a trait Illuminate\Foundation\Testing\Concerns\MakesHttpRequests which should allow you to make all kinds of requests.

But I haven't tested this yet. Please do let me know whether this works or not.

praditha commented 8 years ago

@andizzle Yes, I've tried it, but no luck. There is an error like this: Notice: Undefined property: FeatureContext::$app in vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php line 504

I've tried to setup the $app like in TestCase class $app = require __DIR__.'/../../bootstrap/app.php'; $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

And the result is behat read the .env file instead of .env.behat

andizzle commented 8 years ago

@praditha I'll give it a try over the weekend. In the mean time give this a try in FeatureContext.php:

   public function __construct() {
        global $app;
        $this->app = app();
        $app = $this->app;
    }

This works in L5.0.

eriksape commented 8 years ago

in 5.1 this works in the FeatureContext.php

public function __construct()
{
        parent::setUp();
}
alnutile commented 8 years ago

I tend to do what @andizzle did but really want to try what @eriksape did

praditha commented 8 years ago

@andizzle I've tried it, and there is another error Notice: Undefined property: FeatureContext::$baseUrl in vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php line 594

@eriksape It's not working on Laravel 5.2, it shows an error: Call to undefined method Behat\MinkExtension\Context\MinkContext::setup() As on the tutorial, I extends MinkContext to FeatureContext class.

praditha commented 8 years ago

Ah finally I can solved it on Laravel 5.2.

I use the @andizzle solutions with adding some code to register the $baseUrl, here is a piece of code.

use Illuminate\Foundation\Testing\Concerns\MakesHttpRequests;

Class FeatureContext extends MinkContext implements Context, SnippetAcceptingContext
{
    use DatabaseTransactions;
    use MakesHttpRequests;

    public function __construct()
    {
        global $app;
        $this->app = app();
        $app = $this->app;

        global $baseUrl;
        $this->baseUrl = url('/');
        $baseUrl = $this->baseUrl;
    }
    ...
}
alnutile commented 8 years ago

Is there a PR for this?

thecodingwhale commented 8 years ago

Just to confirm I also experience the same issue reported by @doug7410 regarding of switching on/off the @javascript.

For now I will try your suggestion @alnutile just to make it work in my local machine, Thanks.

edmundluong commented 7 years ago

+1 ran into this problem today while using Selenium and Behat. Hopefully there will be a PR for this but I will give one of the solutions in this thread a shot.

@alnutile Were you ever able to get this working on Laravel 5.3+?

grEvenX commented 7 years ago

@koenvu Did you manage to sort your issues with inserting to database in Background? We're facing the same issue, migration and transaction is run after, so won't work correctly :(

alnutile commented 7 years ago

@edmundluong did you tru @artaaa solution

sepehr commented 7 years ago

Frankly, I can't see how having Laravel to do the request can help here. It's totally irrelevant. The whole point of using Selenium is to enable the tests to exercise applications implemented in Javascript. What if your front is implemented with Vue?

When not using a browser simulator, there's just one instance of Laravel running that uses the .env.behat file. But when using a Javascript session, requests are dispatched by the browser, not PHP and so there's no way for Laravel or this package to detect the testing environment.

All other workarounds are assuming that local/production and testing environment are both using the same database connection, which is not my case.

To alleviate this, we need to somehow create a link between the Behat testing session and Laravel. For example; passing a querystring, e.g. behat=true, when dispatching each request. Then on the Laravel side, we need to detect the signal and load the appropriate env file accordingly which will affect the production code. Ah, it's all messed up...

IMO, this is a serious issue. It makes testing Javascript applications literally impossible. And that's not only about databases, but anything that can be related to some kinda state; env configs, databases, caches, queues, etc.

Related: Behat testing in laravel with javascript - not using test environment

Update; It's interesting how Dusk handles this: How Laravel Dusk handles environments

Update; So, inspired by Dusk, to workaround preserving the environment we can use a trait like this:

trait PreserveEnvironment
{
    /**
     * @BeforeScenario
     */
    public static function backupAndPreserve()
    {
        rename('.env', '.env.bak');
        copy('.env.behat', '.env');
    }

    /**
     * @AfterScenario
     */
    public static function restore()
    {
        unlink('.env');
        rename('.env.bak', '.env');
    }
}

If you use this trait in your FeatureContext class, then any request dispatched from Selenium or any other browser emulator managed by Mink will meet your .env.behat file (as it's now renamed to .env). Then after exercising each scenario, the original .env file will be restored.

It solves a lot of problems, but still... there are shortcomings. Assume you log a user in using Auth::login() before you dispatch the request and you want to assert that she can access some protected pages.

Update; Dusk has also a solution for the authentication. See InteractsWithAuthentication trait.

I'm coming up with a quick solution; will update asap.

sepehr commented 7 years ago

And here it is:
https://github.com/sepehr/behat-laravel-js

Seems to be a solution for all the problems I was facing; the readme file goes through them all.

As I was in middle of a project and needed to use the traits, I just wrote a separate package instead of submitting a pull request. If you think it could be a good fit for this package, I'll be more than willing to submit a pull request.

osman-mohamad commented 7 years ago

@sepehr can you make pull request to this package . because the new package features integrate between laravel and behat plus javascript .

thank you

sepehr commented 7 years ago

Sure @osman-mohamad; Actually, it's a long overdue task in my contribution todo list. Will adapt the code to the extension and do a pull request as soon as I find some free time.

Meanwhile, you can install it as a separate package alongside with behat-laravel-extension.

versedi commented 5 years ago

How this is supposed to work? It only makes sure your env.behat will be used but the transactions still won't be visible for browser's sessions since it's a new php process.

Does it use same database? Yes

Can it access data seeded in test preStep/preScenario/preFeature and rolledback on after* No.

Edit: All right, It requires Database Migration so this is a big no,no if you are working on DB size bigger than xGB.