Given this test:
echo "\n";
echo Session::getId();
$this->call('GET', '/');
echo "\n";
echo Session::getId();
The output shows that the Session ID is not persisted after the request:
fb7e02798f043fac798a424547f0d01acd0dbdc0
83133c07abdbba5bc32f74eaf14362a69406ca45
As far as I can tell they should be the same, app/config/session settings
'driver' => env('SESSION_DRIVER', 'file'),
'lifetime' => 120,
'expire_on_close' => false,
'domain' => "test.com",
The same tests works in 4.2, not entirely sure if there is an additional requirement to use sessions in unit tests or I should be using the facade implementations.
edit: 0 session issues browsing normally on the site
phpunit.xml
<env name="APP_ENV" value="local"/>
<env name="CACHE_DRIVER" value="file"/>
<env name="SESSION_DRIVER" value="file"/>
<env name="QUEUE_DRIVER" value="sync"/>
Unit testing is about testing single classes
I think the intention of unit testing is not fully understood in this example.
Unit testing is not about touching classes you don't want to test. Therefor it should not include third party libraries or resources and it should be about just one class.
Every other code dependency should be implemented with a test double.
If I would include the concrete session in my unit test, I would actually test that object too. Why would I? It means that if Laravel changes the session implementation, I would have to change my unit tests.
Which would mean I would lose the advantage of the unit test, because it should be about change that happens to just one class. With losing the advantage I will lose the benefit of the test, to see if other classes still work after a change.
Integration testing
The visit method is more like integration testing, since it loads the routing mechanism, controller and other dependencies defined in the controller.
You can manage the session data by setting up the expected values for each test case (or test method).
For example, if you have a checkout process which contains a form in each checkout step:
public function testFormStep1()
{
//$this->visit...
}
public function testFormStep2()
{
$this->session([
//... data from step 1 you need in step 2
]);
//$this->visit...
}
In this way you have control over each test case. You don't want to depend on other test cases, test should be done in isolation.
So for example, if step 1 of a form can only pass to step 2 when all form fields are present, you can simply make another test case which simulates fields are left empty in step 1:
public function testFormStep2Error()
{
$this->session([
//... incomplete data from step 1
]);
//... verify form step 2 should do something when form in step 1 is not completed
}
Selenium
Another option is Selenium, which exists more at the level of the system, because it includes a browser and goes through the web server with an HTTP request. The session is bound to the browser by a session cookie (and id). In this way you can preserve the session for each request.
Unit tests run in CLI/terminal/commandline. Cli does not work with persistent sessions.
When running tests, Laravel will automatically set the configuration environment to testing. Laravel automatically configures the session and cache to the array driver while testing, meaning no session or cache data will be persisted while testing.
See: http://laravel.com/docs/5.1/testing
Actually this is almost the same message as in version 4.2. So no idea how you could have made persistent sessions in unit tests before.
Related
A lot of this is wrapped in commands, but I've left that part out to make the problem more feasible.
Consider these two tests:
# Test1: Test login for user
- Step1: Logs in manually (go to login-URL, fill out credentials and click 'Log in').
- Step2: Save auth-cookies as fixtures.
# Test2: Test something is dashboard for user.
- Step1: Set auth-cookies (generated in Test1)
- Step2: Visits https:://example.org/dashboard and ensures the user can see the dashboard.
If they run as written as listed above, then everything is fine.
But if Test2 runs before Test1, then Test2 will fail, since Test1 hasn't to generated the cookies yet.
So Test1 is kind of a prerequisite for Test2.
But Test1 doesn't need to run every time Test2 runs - only if the auth-cookies aren't generated.
I wish I could define my Test2 to be like this instead:
Test2: Test something is dashboard for user.
- Step1: Run ensureAuthCookiesExists-command
- Step2: If the AuthCookies.json-fixture doesn't exist, then run Test1
- Step3: Sets auth-cookies (generated in Test1)
- Step4: Visits https:://example.org/dashboard and ensures the user can see the dashboard.
Solution attempt 1: Control by order
For a long time I've done this using this answer: How to control order of tests. And then having my tests defines like this:
{
"baseUrl": "http://localhost:5000",
"testFiles": [
"preparations/*.js",
"feature-1/check-header.spec.js",
"feature-2/check-buttons.spec.js",
"feature-3/check-images.spec.js",
"feature-4/check-404-page.spec.js",
//...
]
}
But that is annoying, since it means that I keep having to add to add new features to that list, which get's annoying.
And this only solves the problem if I want to run all the tests. If I want to run preparations.spec.js and thereafter: feature-2/check-buttons.spec.js. Then I can't do that easily.
Solution attempt 2: Naming tests smartly
I also tried simply naming them appropriately, like explain here: naming your tests in Cypress.
But that pollutes the naming of the tests, making it more cluttered. And it faces the same issues as solution attempt 1 (that I can't easily run two specific tests after one another).
Solution attempt 3: Making a command for it
I considered making a command that tests for it. Here is some pseudo-code:
beforeEach(() => {
if( preparationsHasntCompleted() ){
runPreparations();
}
}
This seems smart, but it would add extra runtime to all my tests.
This may not suit your testing aims, but the new cy.session() can assure cookie is set regardless of test processing order.
Use it in support in beforeEach() to run before every test.
The first test that runs (either test1 or test2) will perform the request, subsequent tests will use cached values (not repeating the requests).
// cypress/support/e2e.js -- v10 support file
beforeEach(() => {
cy.session('init', () => {
// request and set cookies
})
})
// cypress/e2e/test1.spec.cy.js
it('first test', () => {
// the beforeEach() for 1st test will fire the request
...
})
// cypress/e2e/test2.spec.cy.js
it('second test', () => {
// the beforeEach() for 2nd test will set same values as before from cache
// and not resend the request
})
Upside:
performing login once per run (ref runtime concerns)
performing tests in any order
using the same token for all tests in session (if that's important)
Downside:
if obtaining auth cookies manually (vi UI), effectively moving the login test to a beforeEach()
Example logging in via request
Rather than obtaining the auth cookie via UI, it may be possible to get it via cy.request().
Example from the docs,
cy.session([username, password], () => {
cy.request({
method: 'POST',
url: '/login',
body: { username, password },
}).then(({ body }) => {
cy.setCookie('authToken', body.token)
})
})
It is generally not recommended to write tests that depend on each other as stated in the best practices. You can never be sure that they run correctly. In your case if the first test fails all the other ones will fail, even if the component/functionality is functioning propperly. Especially if you test more than just the pure login procedure, e.g. design.
As #Fody said before you can ensure being logged in in the beforeEach() hook.
I would do it like this:
Use one describe to test the login via UI.
Use another describe where you put the login (via REST) in the before() and the following command Cypress.Cookies.preserveOnce('<nameOfTheCookie>'); in the beforeEach() to not delete the test for the following it()s
I am trying to test the following bit of code:
DimonaClient is just a simple wrapper around a Laravel HttpClient; simplified function here:
The getDeclaration() response is a \Illuminate\Http\Client\Response
What I am trying to do in my test is:
Mock the DimonaClient class so I don't create an actual api call
"Mock" (use Laravel's Http::response()) the response I want so that I can test that a 200 w/ certain statuses dispatches the appropriate event (also mocked, but not relevant here)
My test code looks like this:
My issue(s) seem to be:
the getDeclaration() has an expectation of Illuminate\Http\Client\Response but I can't seem to create anything that will satisfy that (a new Response wants a MessageInterface, etc, etc... )
I don't actually need getDeclaration() to return anything for my testing, so I wonder if I should be mocking this differently in any case (I base this assumption on Http::response handling the internal code I'm testing for things like $response->ok(), instead of a Mockery expectation)
I feel like I'm one small step away from making this work, but going round in circles trying to hook it up correctly.
TIA!
If you are using Http Facade, you don't need to mock DimonaCient. You are nearly there with your test, but let me show you what you would have done:
/** #test */
public function it_can_handle_an_approved_submission(): void
{
Http::fake([
'*' => Http::response([
'declarationStatus' => [
'result' => DimonaDeclarationStatus::ACCEPTED,
'dimonaPeriodId' => $this->faker->numerify('############'),
],
],
]);
$dimonaDeclarationId = $this->faker->numerify('############');
// Do your normal call, and then assertions
}
Doing this, you will tell Http to fake any URL, because we are using *. I would recommend you use $this->endpoint/$declarationId so if it does not match, you will also know you did not hit the right endpoint.
I am not sure what Laravel you are using but this is available since Laravel 6+, check Http fake URLs.
Is it possible to use JGiven (with or without Spring support) to retrieve the statements before / during execution? For example, if we had a fairly typical login acceptance test i.e.
public class LoginFeatureTest extends SpringScenarioTest<GivenIAmAtTheLoginPage, WhenILogin, ThenTheLoginActionWillBeSuccessful> {
#Test
public void my_login_test() {
given().I_am_a_new_user()
.and().I_am_at_the_login_page();
when().I_login_with_username_$_and_password_$("dave", "dave123");
then().the_home_page_is_visible();
}
}
Is it possible to get something access to the following information?
My Login Test (start)
Given I am a new user
and I am at the login page
When I login with username dave and password dave123
Then the home page is visible
My Login Test (end)
i.e. what i'm looking for is: -
The name of a scenario method + all it's given, when, then and and statement calls _(formatted via JGiven formatting).
When each scenario method starts at run-time.
When each given, when, then and and executes at run-time.
When the scenario method ends.
This will give me the ability to visually show in a UI (a) exactly what is going to execute and (2) it's current position during execution (with durations).
12:00:01.012 [ My Login Test (start) ]
12:00:02.035 23ms Given I am a new user
12:00:02.051 16ms and I am at the login page
----> When I login with username dave and password dave123
Then the home page is visible
[ end ]
I'm thinking Spring AOP might come to the rescue here? Or does JGiven provide anything useful buried in it's code?
At the moment there is no possibility to do that. I have created an issue for that: https://github.com/TNG/JGiven/issues/328.
It should not be too difficult to implement this as there is internally already a listener concept. If you are still interested you could propose an API and integrate the mechanism in JGiven.
For an application we use configuration files in which a large number of endpoint characteristics are determined (relations, fillables, visibles, roles, etc.) We would like to loop through these files and conduct automatic tests with PHPUnit, simply to see if we receive a response, if validation errors are being triggered, if the response is in line with the files, etc.
We load the configuration and perform the tests for each endpoint configuration:
public function testConfigurationFiles()
{
$config = resolve('App\Contracts\ConfigInterface');
foreach ($config->resources as $resource=>$configuration) {
foreach ($configuration->endpoints() as $method=>$rules) {
$this->endpoint($method, $resource, $configuration);
}
}
}
After which we use a switch, to test each type of method differently (index, show, create, update, delete). In total this comes down to dozens of tests with hundreds of assertions.
However, if even one of these endpoints fails, the entire tests fails without showing explicit information what went wrong. Is there a way to automatically generate a "test{$resource}{$method}" method for each endpoint, so they will be handled like individual tests?
Besides these tests we also conduct units tests & e2e tests, so we are fully aware of the disadvantages of this way of testing.
After studying PHPUnit some more, I found my answer in dataProviders:
https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
This way you can indicate a data provider for a method, which should return an array with all cases you want to iterate over.
Consider the following stored procedure:
CREATE OR REPLACE FUNCTION get_supported_locales()
RETURNS TABLE(
code character varying(10)
) AS
...
And the following method that call's it:
def self.supported_locales
query = "SELECT code FROM get_supported_locales();"
res = ActiveRecord::Base.connection.execute(query)
res.values.flatten
end
I'm trying to write a test for this method but I'm getting some problems while mocking:
it "should list an intersection of locales available on the app and on last fm" do
res = mock(PG::Result)
res.should_receive(:values).and_return(['en', 'pt'])
ActiveRecord::Base.connection.stub(:execute).and_return(res)
Language.supported_locales.should =~ ['pt', 'en']
end
This test succeds but any test that runs after this one gives the following message:
WARNING: there is already a transaction in progress
Why does this happen? Am I doing the mocking
The database is postgres 9.1.
Your test is running using database level transactions. When the test completes, the transaction is rolled back so that none of the changes made in the test are actually saved to the database. In your case, this rollback can't happen because you have stubbed out the execute method on the ActiveRecord connection.
You can disable transactions globally and switch to using DatabaseCleaner to enable/disable transactions for various tests. You could then set up to use transactions through DatabaseCleaner by default so your existing tests don't change, and then in this one test choose to disable transactions in favor of some other strategy (such as the null strategy since there is no cleaning to be done for this test).
This other SO post indicates you may be able to avoid disabling transactions globally and turn them off on a per test basis as well, I have not tried that myself though.