This question already has answers here:
mocha pass variable to the next test
(3 answers)
Closed 7 years ago.
I'm learning how to write a NodeJS module that works against a vendor's REST API. The critical code for the module itself is written, but now I'm trying to learn how to test it all properly. Currently I'm using MochaJS and ChaiJS for the testing framework. In one test I create a user which returns a random ID, which I need to save. Then later I want to use said ID value and test the user deletion.
Here's the current code that doesn't work:
var names = require('./names.json');
var ids = [];
describe('users', function() {
describe('addUser', function (){
it('should create ' + names[0].firstname, function (done){
this.slow(3000); this.timeout(10000);
api.addUser(names[0],function(x){
x.should.have.property('id').with.length.of.at.least(2);
ids.push(x.id);
done();
});
});
it('should create ' + names[1].firstname, function (done){
this.slow(3000); this.timeout(10000);
api.addUser(names[1],function(x){
x.should.have.property('activated').and.equal(true);
ids.push(x.id);
done();
});
});
});
describe('deleteUser', function (){
for(var a=0;a<ids.length;a++){
it('should delete ' + ids[a], function (done){
api.deleteUser(ids[a],function(x){
x.should.have.property('id').and.equal(ids[a]);
done();
});
});
}
});
});
Even though ids is scoped far outside the testing, the values are not saved. Now I've read other comments on stack overflow about this where the responders basically say "don't re-use values...something something waterfall failure". Which I understand but to me, that's Expected Functionality (TM). If for any reason (either my code or the vendors API) there is a failure and I cannot create a user, then obviously I will not be able to delete a user.
I want to put all this into Travis CI, so I cannot expect a specific user will always be there to delete unless my test framework creates is. I also have a limited number of users on the vendors system, so I need to clean up my testing. There are also other use cases (such as modifying an existing user) that I want to test.
The quick answer is that your for loop never loops.
When the test file is parsed, the for loop (before any test has run and hence before you can push anything into ids) executes and because ids is empty, has no work to do.
To prove this, tweak your code to be:
describe('deleteUser', function (){
console.log("How many IDs?", id);
for(var a=0;a<ids.length;a++){
console.log("This will not be seen...");
it('should delete ' + ids[a], function (done){
api.deleteUser(ids[a],function(x){
x.should.have.property('id').and.equal(ids[a]);
done();
});
});
}
});
The absolute easiest way to fix this is to not loop over the IDs but to instead delete both users, one after the other and then check both were successful:
var names = require('./names.json');
var ids = [];
describe('users', function() {
describe('addUser', function (){
it('should create ' + names[0].firstname, function (done){
this.slow(3000); this.timeout(10000);
api.addUser(names[0],function(x){
x.should.have.property('id').with.length.of.at.least(2);
ids.push(x.id);
done();
});
});
it('should create ' + names[1].firstname, function (done){
this.slow(3000); this.timeout(10000);
api.addUser(names[1],function(x){
x.should.have.property('activated').and.equal(true);
ids.push(x.id);
done();
});
});
});
describe('deleteUser', function (){
it('should delete users', function (done){
api.deleteUser(ids[0],function(x){
x.should.have.property('id').and.equal(ids[0]);
api.deleteUser(ids[1],function(x){
x.should.have.property('id').and.equal(ids[1]);
done();
});
});
});
});
});
Untested and nowhere near great, but should work.
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 months ago.
I encountered axios error in the process of creating my application.
The below code work fine.
in the first image,console.log output res.data.
let categoryId = ''
axios.get('/api/max')
.then((res) => {
console.log(res.data) // image part
categoryId = res.data.id
})
.catch((err) => {
console.log('ssssssuuuuu')
})
('api/max') return category with max ID.
The below code don't work well.
console.log(res) output properly, but console.log(res.data) output undefined.
try {
const res = axios.get('/api/max')
console.log(res)
console.log(res.data) // undefined
categoryId = res.data.id
console.log('a')
} catch (err) {
console.log(err.message)
}
what causes undefined?
I googled but, I didn't know the cause.
I'm sorry that my English is not very good.
Thank you for your help.
You should use await axios.get('/api/max') before using console.log since it's an async call. (you can also use .then but it's less friendly)
Of course, don't forget to wrap your function into an async when using await.
Also, keep in mind that quirk when using console.log. Prefer using Vue devtools to inspect the current state you do have on your page.
Overall, I recommend the usage of async/await over .then all the time.
Here is a nice documentation for that one: https://javascript.info/async-await
I have a typical search where the user types some text in an input, async work is done and a table with the results is properly updated.
I have tests that must wait for this search step and then assert business rules regarding the results, like if the table records are eligible for edit.
Every time I ran a complete test battery (something like 80 test files), one or two of the tests involving that search inevitably fail. But if immediately after that, I run the same test alone, the test passes. It's excruciating and makes e2e testing in CI/CD pointless for the project.
I've read the Cypress documentation about flaky tests and searched questions in StackOverflow and GitHub with only complete failure. It's a drama.
Here is one of the tests:
import { searchList } from '../helpers';
import { createFluxoIniciado, randomFluxoNome } from './common';
import { fluxoSelectors } from './selectors';
describe('fluxos finish', () => {
it('can manually finish a fluxo INICIADO', () => {
// feed data to be searched
const fluxoNome = randomFluxoNome();
createFluxoIniciado({ fluxoNome });
// search
searchList(fluxoNome);
// do something with search results
fluxoSelectors.fluxos.view().click();
fluxoSelectors.finish().click();
fluxoSelectors.confirm().click();
// serach again
searchList(fluxoNome);
cy.contains('FINALIZADO');
});
});
The code in searchList is where trouble emerge sometimes. It uses the callback strategy recommended here. The code attempts to cause retries if not all rows have the searched text.
export function searchList (text) {
cy.get('#searchText')
.scrollIntoView()
.type(text)
.blur();
cy.get('tbody tr').should($trs => {
$trs.each((i, $tr) => {
expect($tr).to.contain(text);
});
}, { timeout: 15000 });
}
Here is an example of a test failure inside a run all test execution:
The problem is obviously caused by the async fetch between .blur() and testing the rows.
You are correctly trying to use Cypress retry with .should(callback), but if the callback is complex or there are multiple steps it may not retry the element that is changing (the table rows).
Ideally you want to keep the cy.get(...).should(...) as simple as possible, and start by testing that the table loading has completed.
// wait for expected number of rows
cy.get('tbody tr', {timeout: 15000}).should('have.length', 5)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
But you have a randomizer there, so maybe it's not possible to test explicitly the row count.
Another approach, test the whole table for text (.contains() checks child text also)
// wait for text to appear somewhere
cy.get('tbody tr', {timeout: 15000}).should('contain', text)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
You can also add an intercept between start and end of api call
export function searchList (text) {
cy.intercept('search/api/endpoint').as('search')
cy.get('#searchText')
.scrollIntoView()
.type(text)
.blur();
cy.wait('#search') // wait for api response
cy.get('tbody tr', {timeout: 15000}).should('contain', text)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
}
I just noticed you have the {timeout} option on .should(), but that's the wrong place,
see Timeouts
cy.get('input', { timeout: 10000 }).should('have.value', '10')
// timeout here will be passed down to the '.should()'
// and it will retry for up to 10 secs
This may be successful
cy.get('tbody tr', { timeout: 15000 })
.should($trs => {
$trs.each((i, $tr) => {
expect($tr).to.contain(text);
});
})
I'm wanting to log into an app, run several searches from test data, then log out. I don't want to login and out for each item in the data set, which would be the case if I coded this way...
dataSet.forEach(data =>{
test('Search Test', async t => {......
I would like to be able to...
test('Search Test', async t => {......
foreeach(data in Data set)
call a function to search
call a function to verify search return.
Something like this...
test('Simple Search Test', async t => {
//await t
await loginPage.login(loginName, password);
await t
.expect(getURL()).contains('home')
// Check logged in user display...
.expect(pageHeader.userName.withText(data.loggedInUser).visible).ok()
dataSet.forEach(data =>{
leftSidebar.searchWithCriteria(data.criteria, 'Filename');
recordNav.verifyTotal(data.srchresult);
});
// Log out
await pageHeader.logout();
await t
.expect(loginPage.copyRight.visible).ok();
});
enter code here
I've tried everything, but can't get it to work. Is this possible or does the entire test have to be run for each data record in the set?
TestCafe allows you to loop through test code in any manner, including iterating through custom data.
To help us determine why this does not work for you, please provide an example that I can run on my machine (including the test code, page object, and the tested page's URL).
I got it to work using this...
for (var i = 0; i < dataSet.length; i++){
leftSidebar.searchWithCriteria(dataSet[i].criteria, 'Filename');
recordNav.verifyTotal(dataSet[i].srchResult);
}
I am reading through the documentation in Cypress and I think I have an idea as to what then() does. It works like promises, where a promise returns another promise, but with then(), we are returning a new subject.
If we look at the code example below, we are using then() because we are returning a new variable, which in this case is called target.
Am I understanding this correctly? If not, can someone correct me?
it.only('Marks an incomplete item complete', () => {
//we'll need a route to stub the api call that updates our item
cy.fixture('todos')
.then(todos => {
//target is a single todo, taken from the head of the array. We can use this to define our route
const target = Cypress._.head(todos)
cy.route(
"PUT",
`api/todos/${target.id}`,
//Here we are mergin original item with an object literal
Cypress._.merge(target, {isComplete: true})
)
})
.then is used to receive the results from cy.fixture('todos'). The variable target is not significant in this code.
In your code sample, the variable that is returned from cy.fixture is named todos - the spacing of the code may be throwing you off here? The .then call is attached to the cy.fixture() call
// These 2 code blocks are the same - just different spacing
cy.fixture('todos')
.then(todos => {});
cy.fixture('todos').then(todos => {});
https://docs.cypress.io/api/commands/fixture.html#Usage
cy.fixture('logo.png').then((logo) => {
// load data from logo.png
})
Using .then() allows you to use the yielded subject in a callback function and should be used when you need to manipulate some values or do some actions.
To put it simply, it is used to play around with the yield of the previous command and work around with it in that case. THEN() command is handy and helpful in debugging the yield of the previous command.
const baseURL = "https://jsonplaceholder.typicode.com";
describe("Get Call-Expect+ normal req", () => {
it("GetPostById-Expect", () => {
cy.request(baseURL + "/posts/1").as("GetPostById");
cy.get("#GetPostById").then((response) => {
//response: status
expect(response.status).to.equal(200);
expect(response.status).to.eq(200);
});
});
Refer: https://docs.cypress.io/api/commands/then#Promises
My thing is a small project.
In main what it does is that the "server" will get a call from the link directly what will run some functions that will update the database and the data that has to be shown.
I will show what I mean:
function updateData(){
connection.query(`SELECT * FROM muzica WHERE melodie = "${updateList()}"`, function (error, rezultat, fields) {
if (error) {console.log('err la selectare')};
//express output
let data = {
melodie: rezultat[0].melodie,
likes: rezultat[0].likes
}
console.log(data.likes);
app.get('/like', (req,res) =>{
res.json(`${data.likes}`);
});
}
setInterval(()=>{
updateData();
}, 20000)
Uhh, how to explain it, I'm so bad at this...
So, in main, I'm new to back-end work, everything that I did was based on their Documentation as I learn way faster by my needs than some guides and so on.
So, when I or someone does my http://website/like it should show just data.likes, cause that is all that I need, don't count data.melodie (i will clean that later on) after I finish all the code.
Anyway, whenever I do website/like data.likes is not updating to the new database data.likes.
For example, data.likes before were 5, in a few minutes it can be 2 but whenever I call website/like show "5" than its new value 2.
Don't be hash on me, I'm new and I want to learn as much as I can, but I can't understand the above case, by my logic it should ALWAYS show what its in database when it refreshes each 10 seconds(I run this in localhost so I will not stress any online server).
But if there is any better way to check for databases update than "setInterval" please notice me.
It's hard to learn alone without a mentor or someone else to talk about this domain.
Thank you for your time!
Kind regards,
Pulsy
You have things a bit inside out. A request handler such as app.get('/like', ...) goes at the top level and you only ever call it once. What that statement does is register an event handler for any incoming requests with the /like path. When the server receives an incoming request for /like, it will then call the function for this route handler.
You then put inside that route handler the code that you want to run to generate the response and send the response back to the client.
app.get('/like', (req, res) => {
connection.query(`SELECT * FROM muzica WHERE melodie = "${updateList()}"`, function (error, rezultat, fields) {
if (error) {
console.log(error);
res.sendStatus(500);
} else {
//express output
let data = {
melodie: rezultat[0].melodie,
likes: rezultat[0].likes
}
res.json(data);
}
});
});
The endpoints need to be outside of any functions in express.
For example, if you look at the express "hello world" example here, you will see that they have a basic app that only has a single GET endpoint defined which is "/" so you would access it by running "localhost/" or "127.0.0.1/".
In your case, you want your endpoint to be "/like", so you must define something like:
const express = require('express')
const app = express()
const port = 3000
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
app.get('/like', (req, res) => {
// do database stuff and assign data variable
// res.json(data);
}