Is it possible to use cypress for testing relationship between models?
For example, I have this kind of relationship: a teacher has many students, each student belongs to a teacher. After the teacher A logged in, at url "/my-students", he or she will see a list of all his or her students.
What I want to test is to make sure none of the students listed on "/my-students" page belong to teacher B than teacher A.
Can I test this case with cypress? Is it possible and how to do it if it's possible?
The short answer is "yes" you can absolutely do this kind of testing. There are dozens of ways, but I'm going to suggest what I consider to be the simplest approach.
Make sure the data being used by your website doesn't change. You want your tests to be deterministic... none of this run a database query to determine the expected results stuff.
The first time through, verify the page contents manually
Use cy.snapshot() to record the current page state for future comparison. This is an additional npm package from Gleb Bahmutov (a Cypress developer). Full instructions, including installation, can be found here.
Your hypothetical test would look something like this:
describe('student directory page', () => {
beforeEach(() => {
// Log in
cy.logIn('lizstewart#example.com') // This is usually a custom command; up to you
})
it('displays the correct students', () => {
// Go to the page
cy.visit('my-students')
// Check for the correct students
cy.get('#studentList').snapshot()
})
})
The first time the test runs it will pass no matter what, and will write out a file titled snapshots.js, which you can commit to your repo. All subsequent test runs will fail if the HTML output doesn't match the previous content exactly.
It's a blunt approach, but it's quick and effective.
Related
I'm working on a React app and our team is writing Cypress tests for it. We want to test basic CRUD functionality, so we have created three tests:
CREATE user
UPDATE user
DELETE user
The CREATE test uses a fixture to define the new user.
The UPDATE and DELETE tests reference that same fixture and update and delete the user that was created in the previous test. This means the UPDATE and DELETE tests are dependent on the CREATE test. Is this ok?
Based on what I'm reading, it sounds like each Cypress test should be able to run independently and I can see that's not the case here.
However, what is the best way to do that?
I don't think it makes sense to copy and paste the same 10-20 lines of code from the CREATE test and duplicate them in both the UPDATE and DELETE tests. Is there a way we can abstract the CREATE test and include it in the UPDATE and DELETE tests?
Or should we just combine all three tests into a single "CRUD user" test?
Based on what I'm reading, it sounds like each Cypress test should be able to run independently.
Ideally, yes. There are several reasons for that:
in theory, if one test fails, the other ones can still pass; but if your test that creates some entity (user in your case) fails, the other ones will fail for sure, but not because of what they focus on
you don't really have a way to ensure the tests will be executed in the same order; although in Cypress I think it's based on alphabetical order, but this might not apply with other tools or even with Cypress in the future; or you decide to add some test somewhere which changes the order of tests and all of a sudden some tests will fail and you won't instantly know why
I don't think it makes sense to copy and paste the same 10-20 lines of code from the CREATE test and duplicate them in both the UPDATE and DELETE tests. Is there a way we can abstract the CREATE test and include it in the UPDATE and DELETE tests?
Cypress offers for example commands, so your final test code can look like this:
it('CREATE user', () => {
cy
.fixture('user')
.then(user => {
cy
.createUser(user)
// whatever checks you need to perform here
.its('response.statusCode')
.should('eq', 201);
});
});
then if your 10-20 lines of code are hidden behind cy.createUser(), you can easily reuse that in other tests:
it('DELETE user', () => {
cy
.fixture('anotherUser')
.then(anotherUser => {
cy
.createUser(anotherUser)
.then(response => {
cy
.deleteUser(response.body.id)
// whatever checks you need to perform
.its('response.statusCode')
.should('eq', 204);
});
});
});
I think such code reads quite easily and the tests are short as well, so it is easily maintained if need be.
I'm using Laravel for my project and I'm new to unit/feature testing so I was wondering what is the best way to approach more complicated feature cases when writing tests?
Let's take this test example:
// tests/Feature/UserConnectionsTest.php
public function testSucceedIfConnectAuthorised()
{
$connection = factory(Connection::class)->make([
'sender_id' => 1,
'receiver_id' => 2,
'accepted' => false,
'connection_id' => 5,
]);
$user = factory(User::class)->make([
'id' => 1,
]);
$response = $this->actingAs($user)->post(
'/app/connection-request/accept',
[
'accept' => true,
'request_id' => $connection->id,
]
);
$response->assertLocation('/')->assertStatus(200);
}
So we've got this situation where we have some connection system between two users. There is a Connection entry in the DB created by one of the users. Now to make it a successful connection the second user has to approve it. The problem is within the UserController accepting this through connectionRequest:
// app/Http/Controllers/Frontend/UserController.php
public function connectionRequest(Request $request)
{
// we check if the user isn't trying to accept the connection
// that he initiated himself
$connection = $this->repository->GetConnectionById($request->get('request_id'));
$receiver_id = $connection->receiver_id;
$current_user_id = auth()->user()->id;
if ($receiver_id !== $current_user_id) {
abort(403);
}
[...]
}
// app/Http/Repositories/Frontend/UserRepository.php
public function GetConnectionById($id)
{
return Connection::where('id', $id)->first();
}
So we've got this fake (factory created) connection in the test function and then we unfortunately are using its fake id to run a check within the real DB among real connections, which is not what we want :(
Researching I found an idea of creating interfaces so then we can provide a different method bodies depending if we're testing or not. Like here for GetConnectionById() making it easy to fake answers to for the testing case. And that seems OK, but:
for one it looks like a kind of overhead, besides writing tests I have to make the "real" code more complicated itself for the sole purpose of testing.
and second thing, I read all that Laravel documentation has to say about testing, and there is no one place where they mention using of interfaces, so that makes me wonder too if that's the only way and the best way of solving this problem.
I will try to help you, when someone start with testing it is not easy at all, specially if you don't have a strong framework (or even a framework at all).
So, let me try help you:
It is really important to differentiate Unit testing vs Feature testing. You are correctly using Feature test, because you want to test business logic instead of a class directly.
When you test, my personal recommendation is always create a second DB to only use with tests. It must be completely empty all the time.
So, for you to achieve this, you have to define the correct environment variables in phpunit.xml, so you don't have to do magic for this to work when you only run tests.
Also, use RefreshDatabase trait. So, each time you run a test, it is going to delete everything, migrate your tables again and run the test.
You should always create what you need to have as mandatory for your test to run. For example, if you are testing if a user can cancel an order he/she created, you only need to have a product, a user and an invoice associated with the product and user. You do not need to have notifications created or anything not related to this. You must have what you expect to have in the real case scenario, but nothing extra, so you can truly test that it fully works with the minimum stuff.
You can run seeders if your setup is "big", so you should be using setup method.
Remember to NEVER mock core code, like request or controllers or anything similar. If you are mocking any of these, you are doing something wrong. (You will learn this with experience, once you truly know how to test).
When you write tests names, remember to never use if and must and similar wording, instead use when and should. For example, your test testSucceedIfConnectAuthorised should be named testShouldSucceedWhenConnectAuthorised.
This tip is super personal: do not use RepositoryPattern in Laravel, it is an anti-pattern. It is not the worst thing to use, but I recommend having a Service class (do not confuse with a Service Provider, the class I mean is a normal class, it is still called Service) to achieve what you want. But still, you can google about this and Laravel and you will see everyone discourages this pattern in Laravel.
One last tip, Connection::where('id', $id)->first() is exactly the same as Connection::find($id).
I forgot to add that, you should always hardcode your URLs (like you did in your test) because if you rely on route('url.name') and the name matches but the real URL is /api/asdasdasd, you will never test that the URL is the one you want. So congrats there ! A lot of people do not do this and that is wrong.
So, to help you in your case, I will assume you have a clear database (database without tables, RefreshDatabase trait will handle this for you).
I would have your first test as this:
public function testShouldSucceedWhenConnectAuthorised()
{
/**
* I have no idea how your relations are, but I hope
* you get the main idea with this. Just create what
* you should expect to have when you have this
* test case
*/
$connection = factory(Connection::class)->create([
'sender_id' => factory(Sender::class)->create()->id,
'receiver_id' => factory(Reciever::class)->create()->id,
'accepted' => false,
'connection_id' => factory(Connection::class)->create()->id,
]);
$response = $this->actingAs(factory(User::class)->create())
->post(
'/app/connection-request/accept',
[
'accept' => true,
'request_id' => $connection->id
]
);
$response->assertLocation('/')
->assertOk();
}
Then, you should not change anything except phpunit.xml environment variables pointing to your testing DB (locally) and it should work without you changing anything in your code.
I understand that within this framework users are unable to call scenarios within scenarios.
I am trying to create End to End test cases where there are validation points at multiple stages of the test.
Using 'bad' gherkin syntax the process for the scenario would be something like:
Given Item A exists
When User processes Item
Then Warning is displayed
When User accesses Item
Then Warning is not displayed
When User finalises Item
Then Item status = "CURRENT"
And Record Status = "COMPLETED"
The first thing I considered was breaking the scenario into 3 distinct GWT scenarios.
That is fine.
However suppose I now want to create a new end to end scenario that can re-use one of the 3 scenarios created (as you would re-use a function).
How do I do this without duplication of Gherkin code?
I cannot use Background as the sections that I require to re-use are in the middle of the execution.
Steps prior and after may be different.
IN SUMMARY: I am trying to re-use a GWT scenario that is common for many end to end scenarios where the end to end scenarios are inherently different.
Any feedback or assistance you can provide would be greatly appreciated.
Cheers and thanks,
I think I know what you mean. I have done some "meta" scenario's. For example there are ones that test login, maintaining details and creating an account that are very fine grained. Then I have scenario's that test things further along the way, assuming that the happy path has been taken up to that point. So imagine having a scebnario called:
The user is logged in and has successfully created an account and is at the add product screen
For that, in the step definition, I would just combine function calls with hardcoded values:
#When("^The user is logged in and has successfully created an account and is at the add product screen$")
def addProducts(String val) {
navLibrary.loginAsUser("user", "password")
navLibrary.createAccount("some", "params", "you", "need")
navLibrary.navToProducts("some param")
}
And then start with the finer details in new separate steps. You have to think system design, and cascading. For me it meant a lot of initial rework as the tests started growing, but now it's a breeze. Pick the right level of reusability. It is testing code, so it doesn't have to subscribe perfectly to all the programming precepts. It must work, and it must be low maintenance in the long run.
I didn't use Cypress though. My tests are in Groovy with Selenium and Cucumber.
My situation is this: I have multiple components in my view that ultimately depend on the same data, but in some cases the view state is derived from the data. How do I make sure my whole view stays in sync when the underlying data changes? I'll illustrate with an example using everyone's favorite Star Wars API.
First, I show a list of all the films, with a query like this:
# ALL_FILMS
query {
allFilms {
id
title
releaseDate
}
}
Next, I want a separate component in the UI to highlight the most recent film. There's no query for that, so I'll implement it with a client-side resolver. The query would be:
# MOST_RECENT_FILM
query {
mostRecentFilm #client {
id
title
}
}
And the resolver:
function mostRecentFilmResolver(parent, variables, context) {
return context.client.query({ query: ALL_FILMS }).then(result => {
// Omitting the implementation here since it's not relevant
return deriveMostRecentFilm(result.data);
})
}
Now, where it gets interesting is when SWAPI gets around to adding The Last Jedi and The Rise of Skywalker to its film list. We can suppose I'm polling on the list so that it gets periodically refetched. That's great, now my list UI is up to date. But my "most recent film" UI isn't aware that anything has changed — it's still stuck in 2015 showing The Force Awakens, even though the user can clearly see there are newer films.
Maybe I'm spoiled; I come from the world of MobX where stuff like this Just Works™. But this doesn't feel like an uncommon problem. Is there a best practice in the realm of Apollo/GraphQL for keeping things in sync? Am I approaching this problem in entirely the wrong way?
A few ideas I've had:
My "most recent film" query could also poll periodically. But you don't want to poll too often; after all, Star Wars films only come out every other year or so. (Thanks, Disney!) And depending on how the polling intervals overlap there will still be a big window where things are out of sync.
Instead putting the deriveMostRecentFilm logic in a resolver, just put it in the component and share the ALL_FILMS query between components. That would work, but that's basically answering "How do I get this to work in Apollo?" with "Don't use Apollo."
Some complicated system of keeping track of the dependencies between queries and chaining refreshes based on that. (I'm not keen to invent this if I can avoid it!)
In Apollo observables are (in components) over queried values (cached data 'slots') but your mostRecentFilm is not an observable, is not based on cached values (they are cached) but on one time fired query result (updated on demand).
You're only missing an 'updating connection', f.e. like this:
# ALL_FILMS
query {
allFilms {
id
title
releaseDate
isMostRecentFilm #client
}
}
Use isMostRecentFilm local resolver to update mostRecentFilm value in cache.
Any query (useQuery) related to mostRecentFilm #client will be updated automatically. All without additional queries, polling etc. - Just Works? (not tested, it should work) ;)
So, I'm not quite sure how I should structure this in CakePHP to work correctly in the proper MVC form.
Let's, for argument sake, say I have the following data structure which are related in various ways:
Team
Task
Equipment
This is generally how sites are and is quite easy to structure and make in Cake. For example, I would have the a model, controller and view for each item set.
My problem (and I'm sure countless others have had it and already solved it) is that I have a level above the item sets. So, for example:
Department
Team
Task
Equipment
Department
Team
Task
Equipment
Department
Team
Task
Equipment
In my site, I need the ability for someone to view the site at an individual group level as well as move to view it all together (ie, ignore the groups).
So, I have models, views and controls for Depart, Team, Task and Equipment.
How do I structure my site so that from the Department view, someone can select a Department then move around the site to the different views for Team/Task/Equipment showing only those that belong to that particular Department.
In this same format, is there a way to also move around ignoring the department associations?
Hopefully the following example URLs clarifies anything that was unclear:
// View items while disregarding which group-set record they belong to
http://www.example.com/Team/action/id
http://www.example.com/Task/action/id
http://www.example.com/Equipment/action/id
http://www.example.com/Departments
// View items as if only those associated with the selected group-set record exist
http://www.example.com/Department/HR/Team/action/id
http://www.example.com/Department/HR/Task/action/id
http://www.example.com/Department/HR/Equipment/action/id
Can I get the controllers to function in this manner? Is there someone to read so I can figure this out?
Thanks to those that read all this :)
I think I know what you're trying to do. Correct me if I'm wrong:
I built a project manager for myself in which I wanted the URLs to be more logical, so instead of using something like
http://domain.com/project/milestones/add/MyProjectName I could use
http://domain.com/project/MyProjectName/milestones/add
I added a custom route to the end (!important) of my routes so that it catches anything that's not already a route and treats it as a "variable route".
Router::connect('/project/:project/:controller/:action/*', array(), array('project' => '[a-zA-Z0-9\-]+'));
Whatever route you put means that you can't already (or ever) have a controller by that name, for that reason I consider it a good practice to use a singular word instead of a plural. (I have a Projects Controller, so I use "project" to avoid conflicting with it.)
Now, to access the :project parameter anywhere in my app, I use this function in my AppController:
function __currentProject(){
// Finding the current Project's Info
if(isset($this->params['project'])){
App::import('Model', 'Project');
$projectNames = new Project;
$projectNames->contain();
$projectInfo = $projectNames->find('first', array('conditions' => array('Project.slug' => $this->params['project'])));
$project_id = $projectInfo['Project']['id'];
$this->set('project_name_for_layout', $projectInfo['Project']['name']);
return $project_id;
}
}
And I utilize it in my other controllers:
function overview(){
$this->layout = 'project';
// Getting currentProject id from App Controller
$project_id = parent::__currentProject();
// Finding out what time it is and performing queries based on time.
$nowStamp = time();
$nowDate = date('Y-m-d H:i:s' , $nowStamp);
$twoWeeksFromNow = $nowDate + 1209600;
$lateMilestones = $this->Project->Milestone->find('all', array('conditions'=>array('Milestone.project_id' => $project_id, 'Milestone.complete'=> 0, 'Milestone.duedate <'=> $nowDate)));
$this->set(compact('lateMilestones'));
$currentProject = $this->Project->find('all', array('conditions'=>array('Project.slug' => $this->params['project'])));
$this->set(compact('currentProject'));
}
For your project you can try using a route like this at the end of your routes.php file:
Router::connect('/:groupname/:controller/:action/*', array(), array('groupname' => '[a-zA-Z0-9\-]+'));
// Notice I removed "/project" from the beginning. If you put the :groupname first, as I've done in the last example, then you only have one option for these custom url routes.
Then modify the other code to your needs.
If this is a public site, you may want to consider using named variables. This will allow you to define the group on the URL still, but without additional functionality requirements.
http://example.com/team/group:hr
http://example.com/team/action/group:hr/other:var
It may require custom routes too... but it should do the job.
http://book.cakephp.org/view/541/Named-parameters
http://book.cakephp.org/view/542/Defining-Routes
SESSIONS
Since web is stateless, you will need to use sessions (or cookies). The question you will need to ask yourself is how to reflect the selection (or not) of a specific department. It could be as simple as putting a drop down selection in the upper right that reflects ALL, HR, Sales, etc. When the drop down changes, it will set (or clear) the Group session variable.
As for the functionality in the controllers, you just check for the Session. If it is there, you limit the data by the select group. So you would use the same URLs, but the controller or model would manage how the data gets displayed.
// for all functionality use:
http://www.example.com/Team/action/id
http://www.example.com/Task/action/id
http://www.example.com/Equipment/action/id
You don't change the URL to accommodate for the functionality. That would be like using a different URL for every USER wanting to see their ADDRESS, PHONE NUMBER, or BILLING INFO. Where USER would be the group and ADDRESS, PHONE NUMBER< and BILLING INFO would be the item sets.
WITHOUT SESSIONS
The other option would be to put the Group filter on each page. So for example on Team/index view you would have a group drop down to filter the data. It would accomplish the same thing without having to set and clear session variables.
The conclusion is and the key thing to remember is that the functionality does not change nor does the URLs. The only thing that changes is that you will be working with filtered data sets.
Does that make sense?