Wait for external event in acceptance tests - ember.js

In an Ember 2.3 app, I'm using stripe and I have this acceptance test :
it('can visit /user-subscription', () => {
visit('/');
andThen(() => {
click('#pay');
andThen(() => {
fillIn('#card_number', '4242424242424242');
fillIn('#cc-exp', '1299');
fillIn('#cc-csc', '444');
click('#submitButton');
andThen(() => {
done();
expect(currentPath()).to.equal('subscriptions.success');
});
});
});
});
When I click on pay button, it show the Stripe box with this code :
var checkout = StripeCheckout.configure({
key: "...",
locale: 'fr'
}).open({
email: owner.get('email'),
amount: price,
token: (result) => {
# ...
}
});
It loads and execute and external script. The test is failing before the box is displayed. The external script is not loaded completely when the test fail.
I'm using EmberCliMirage with this.passthrough('https://checkout.stripe.com/**');.
What can I do to make the test pass?

You can pause the test until you receive the event. https://api.qunitjs.com/async/
let eventReceived = assert.async();
// ... callback for that async event ...
function eventComplete() {
eventReceived();
// ... do some more assertions
}
// supply the callback to something that knows about the external event

Related

Cannot log after tests are done in jestjs

I have written test cases for signin API using jest. After completing all five test of a test suit jest give me following error in log.
Can any body tell Why it is So and how to fix it?
CODE:(signup.test.ts)
import request from 'supertest';
import { TYPES } from '../src/inversify.types'
import { Application } from '../src/app/Application'
import { container } from '../src/inversify.config'
import dotenv from 'dotenv'
import { RESPONSE_CODE } from '../src/utils/enums/ResponseCode'
import { RESPONSE_MESSAGES } from '../src/utils/enums/ResponseMessages'
import { UserSchema } from '../src/components/user/User';
// import jwt from 'jsonwebtoken';
var application: Application
describe("POST / - SIGNUP endpoint", () => {
// var testusers: any;
//This hook is executed before running all test cases, It will make application instance, make it to listen
// on it on port 3000 and add test document in DB
beforeAll(async () => {
// Make enviroment variables available throughout the application
dotenv.config();
// Getting application instance using iversify container
application = container.get<Application>(TYPES.Application);
// Initialize frontside of application
await application.bootstrap();
// Starting Application server on given port
await application.listen(3000);
});
afterAll(
//This hook is executed after running all test cases and delete test document in database
async () =>{
const res = await UserSchema.deleteMany({ Name: { $in: [ "Test User", "Test" ] } });
// `0` if no docs matched the filter, number of docs deleted otherwise
console.log('---------------------->>>>>>>>>>>>>>>>>>>', (res as any).deletedCount);
}
)
it("Signup for user that don\'t exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CREATED);
expect(JSON.parse(response.text)).toEqual(expect.objectContaining({
Message: RESPONSE_MESSAGES.ADDED_SUCESSFULLY,
Data: expect.objectContaining({
Name: 'Test User',
Country: '',
PhoneNumber: '',
// Password: '$2b$10$nIHLW/SA73XLHoIcND27iuODFAArOvpch6FL/eikKT78qbShAl6ry',
Dob: '',
Role: 'MEMBER',
IsEmailVerified: false,
IsBlocked: 'ACTIVE',
IsTokenSent: false,
twoFAStatus: false,
// _id: '5c812e2715e0711b98260fee',
Email: JSON.parse(process.env.TEST_USER).Email
})
})
);
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
it("Signup for user that exists", async () => {
const response = await request(application.getServer()).post('/user/signup')
.send({
"Email": JSON.parse(process.env.TEST_USER).Email,
"Name": "Test User",
"Password": process.env.TEST_ACCOUNTS_PASSWORD
})
expect(response.status).toBe(RESPONSE_CODE.CONFLICT);
expect(JSON.parse(response.text)).toEqual({
Message: RESPONSE_MESSAGES.ALREADY_EXISTS
})
console.log('*** Signup for user that don\'t exists *** response', response.text, 'response status', response.status);
});
});
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't
stopped in your tests. Consider running Jest with
--detectOpenHandles to troubleshoot this issue.
Cannot log after tests are done. Did you forget to wait for something
async in your test?
Attempted to log "{ accepted: [ 'unverifiedtestuser#abc.com' ],
rejected: [],
envelopeTime: 621,
messageTime: 867,
messageSize: 906,
response: '250 2.0.0 OK 1551945300 f6sm5442066wrt.87 - gsmtp',
envelope:
{ from: 'abc#gmail.com',
to: [ 'unverifiedtestuser#abc.com' ] },
messageId: '<45468449-b5c8-0d86-9404-d55bb5f4g6a3#gmail.com>' }".
at CustomConsole.log (node_modules/jest-util/build/CustomConsole.js:156:10)
at src/email/MailHandler.ts:2599:17
at transporter.send.args (node_modules/nodemailer/lib/mailer/index.js:226:21)
at connection.send (node_modules/nodemailer/lib/smtp-transport/index.js:247:32)
at callback (node_modules/nodemailer/lib/smtp-connection/index.js:435:13)
at stream._createSendStream (node_modules/nodemailer/lib/smtp-connection/index.js:458:24)
at SMTPConnection._actionSMTPStream (node_modules/nodemailer/lib/smtp-connection/index.js:1481:20)
at SMTPConnection._responseActions.push.str (node_modules/nodemailer/lib/smtp-connection/index.js:968:22)
at SMTPConnection._processResponse (node_modules/nodemailer/lib/smtp-connection/index.js:764:20)
at SMTPConnection._onData (node_modules/nodemailer/lib/smtp-connection/index.js:570:14)
I was using the react-native default test case (see below) when Cannot log after tests are done happened.
it('renders correctly', () => {
renderer.create(<App />);
});
Apparently, the problem was that the test ended but logging was still needed. So I tried to make the callback in the test case async, hoping that the test won't terminate immediately:
it('renders correctly', async () => {
renderer.create(<App />);
});
And it worked. However, I have very little clue what the inner working is.
If you are using async/await type in your code, then this error can occur when you are calling async function without await keyword.
In my case, I have defined a function like this below,
async getStatistics(headers) {
....
....
return response;
}
But I have called this method like getStatistics(headers) instead of await getStatistics(headers).
When I included await, it worked fine and the issue resolved.
In my case while using nodejs + jest + supertest the problem was that when I import app from "./app" to my test file to do some stuff with supertest (request(app)), I actually import with app.listen() , because when I'm exporting app, export takes in account app.listen() too, but we don't need app.listen() in tests and it throws an error
"Cannot log after tests are done.Did you forget to wait for something async in your test?"
Here is all in one file(that's the problem!)
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});
export default app;
To solve this issue I created 2 separate folders:
// 1) app.ts
Where I put all stuff for my const app = express(), routes etc and export app
dotenv.config();
const app = express();
app.use(express.json());
// ROUTES
app.get("/api", (req, res) => {
res.json({ message: "Welcome to Blog API!" });
});
app.use("/api/users", usersRoutes);
app.use("/api/blogs", blogsRouter);
export default app;
// 2) index.ts
Where I put app.listen() and mongoose.connection() and import app
*import mongoose from "mongoose";
import app from "./app";
// The server will start only if the connection to database is established
mongoose
.connect(process.env.MONGO_URI!)
.then(() => {
console.log("MongoDB est connecté");
const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`The server is running on port: ${port}`));
})
.catch(err => {
console.log(err);
});*
For me I needed to add an await before the expect() call also to stop this error (and an async before the test() callback function).
Also caused and fixed Jest not detecting coverage on the lines in the code throwing the error!
test("expect error to be thrown for incorrect request", async () => {
await expect(
// ^ added this
async () => await getData("i-made-this-up")
).rejects.toThrow(
"[API] Not recognised: i-made-this-up"
);
});
getData() returns an Axios call and in this case an error is caught by catch and re-thrown.
const getData = async (id) => {
return await axios
.get(`https://api.com/some/path?id=${id}`)
.then((response) => response.data)
.catch((error) => {
if (error?.response?.data?.message) {
console.error(error) // Triggered the error
throw new Error("[API] " + error.response.data.message);
}
throw error;
});
};
This happened to me because I had an infinite loop while (true). In my case, I was able to add a method for setting the value of the loop based on user input, rather than defaulting to true.
In my case, the error was caused by asynchronous Redis connection still online. Just added afterall method to quit Redis and could see the log again.
Working on Typescript 4.4.2:
test("My Test", done => {
let redisUtil: RedisUtil = new RedisUtil();
let redisClient: Redis = redisUtil.redis_client();
done();
});
afterAll(() => {
redisClient.quit();
});
I solved it with the env variables:
if (process.env.NODE_ENV !== 'test') {
db.init().then(() => {
app.listen(PORT, () => {
console.log('API lista por el puerto ', PORT)
})
}).catch((err) => {
console.error(err)
process.exit(1)
})
} else {
module.export = app
}
I faced same warnings. However the fix is bit weird:
The jest unit test script import a function (which is not export from src/). After I added the export to the function to be tested. The error disappears.
I had a similar issue:
Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ".
It was due to a missing static keyword. This code caused the issue:
class MyComponent extends React.Component<Props, State> {
propTypes = {
onDestroy: PropTypes.func,
}
}
It should have been:
class MyComponent extends React.Component<Props, State> {
static propTypes = {
onDestroy: PropTypes.func,
}
}

Vue.js unit test - mock service with async data

Project: https://github.com/marioedgar/webpack-unit-test
I have a Vue.js app I generated with the vue CLI. I've only edited the HelloWorld component slightly to fetch some async data from my test service, you can see that here:
<template>
<h1>{{ message }}</h1>
</template>
<script>
import service from './test.service'
export default {
name: 'HelloWorld',
created () {
service.getMessage().then(message => {
this.message = message
})
},
data () {
return {
message: 'A'
}
}
}
</script>
<style scoped>
</style>
The test service lives in the same directory and is very simple:
class Service {
getMessage () {
return new Promise((resolve, reject) => {
console.log('hello from test service')
resolve('B')
})
}
}
const service = new Service()
export default service
So in order to mock this service, im using the webpack vue-loader to inject the mock service as described in the official documentation here:
https://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
So here is my test which is almost identical to the example:
import Vue from 'vue'
const HelloInjector = require('!!vue-loader?inject!../../../src/components/HelloWorld')
const Hello = HelloInjector({
// mock it
'./test.service': {
getMessage () {
return new Promise((resolve, reject) => {
resolve('C')
})
}
}
})
describe('HelloWorld.vue', () => {
it('should render', () => {
const vm = new Vue({
template: '<div><test></test></div>',
components: {
'test': Hello
}
}).$mount()
expect(vm.$el.querySelector('h1').textContent).to.equal('C')
})
})
There are two issues i am facing:
The test fails because the assertion is executing before the mocked promise is resolved. From my understanding, this is because the vue lifecycle hasnt completely finished when im doing my asserting. The common patter to wait for the next cycle would be to wrapp my assertion around the next tick function like this:
it('should render', (done) => {
const vm = new Vue({
template: '<div><test></test></div>',
components: {
'test': Hello
}
}).$mount()
Vue.nextTick(() => {
expect(vm.$el.querySelector('h1').textContent).to.equal('C')
done()
})
})
This, however, does not work unless I nest 3 nextTicks, which seems extremely hacky to me. Is there something I am missing to get this to work? this example seems extremely straightforward, but I cannot get this test to pass without lots of nextTicks
I keep getting a strange error, intermittently... this warning shows up probably 50% of the time and is not consistant at all.
[vue warn] Failed to mount component: template or render function is not defined
Again, this happens only sometimes. I can run the same exact unit test without any changes and it will show me this message 50% of the time.
I truly couldn't figure out why sometimes the component failed to mount. I'm not even sure it's related to the injector but, in any case, I kept the test consistent by not using it; trying a different approach instead.
The component might be more testable if the service is injected through the props instead of being directly used.
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script>
import service from './test.service'
export default {
name: 'HelloWorld',
created () {
this.service.getMessage().then(message => {
this.message = message
})
},
data () {
return {
message: 'A'
}
},
props: {
service: {
default: service
}
}
}
</script>
<style scoped>
</style>
This makes the injector unnecessary as the mocked service can be instead passed to the component using propsData in the constructor.
import Vue from 'vue'
import Async from '#/components/Async'
const service = {
getMessage () {
return new Promise((resolve, reject) => {
resolve('C')
})
}
}
describe('Async.vue', () => {
let vm
before(() => {
const Constructor = Vue.extend(Async)
vm = new Constructor({
propsData: {
service: service
}
}).$mount()
})
it('should render', function () {
// Wrapping the tick inside a promise, bypassing PhantomJS's lack of support
return (new Promise(resolve => Vue.nextTick(() => resolve()))).then(() => {
expect(vm.$el.querySelector('h1').textContent).to.equal('C')
})
})
})

Testing vuex action that contains an async

[This is a Vue app, using Vuex, created with vue-cli, using mocha, chai, karma, sinon]
I'm trying to create tests for my vuex state and I DON'T want to use a mock -- one of my big goals for these tests is to also test the API that data is coming from.
I am trying to follow the docs for chai-as-promised.
This is a simplification of the vuex action I'm trying to test:
const actions = {
login: (context, payload) => {
context.commit('setFlashMessage', "");
axios.get("https://first-api-call")
.then((response) => {
axios.post("https://second-api-call")
.then((response) => {
router.push({ name: "Home"});
context.commit('setFlashMessage', "Logged in successfully");
context.commit('setLogin', response.data);
});
},
Notice that the login action has two promises and doesn't return anything. The login action does two things: it sets some state and it changes the route.
The example that I've seen that using chai-as-promised expects that the promise is returned. That is:
var result = systemUnderTest();
return expect(result).to.eventually.equal(blah);
But in my case, login() doesn't return anything, and I'm not sure what I would return if it did.
This is what I have so far:
import store from '#/src/store/store'
describe('login', () => {
it('bad input', () => {
store.login({ username: "abcd", password: ""});
// What is the test I should use?
}
}
I would return the login response message and make two tests. One to make sure that invalid credentials return a failure message and one to make sure that valid credentials login successfully
My co-worker and I came up with the solution:
The vuex action needs to return the promise, and they can be chained together:
login: (context, payload) => {
context.commit('setFlashMessage', "");
return axios.get("https://first-api-call")
.then((response) => {
return axios.post("https://second-api-call")
})
.then((response) => {
// etc...
router.push({ name: "Home"});
context.commit('setFlashMessage', "Logged in successfully");
context.commit('setLogin', response.data);
return {status: "success"};
});
},
Then we didn't need chai-as-promised because the test looks like this:
it('bad password', () => {
const result = store.dispatch("login", { username: userName, password: password + "bad" });
return result.then((response) => {
expect(response).to.deep.equal({ status: "failed"});
store.getters.getFlashMessage.should.equal("Error logging in");
});
});

React unit testing

I am trying to write test cases of react component in Mocha + Enzyme. I have used Sinon to spy document elements.
In a react component method i have below statement, where trying to add a value for Select element on Button click event.
handleClick(e){
e.preventDefault();
if(e.target.value!='select')
this.setState({
selectedValue: e.target.value
})
else
document.getElementsByName("foodSelector")[0].value="select";
}
So i tried mocking this method as below using Mocha,
describe('(Container) ReactContainer', () => {
beforeEach(function(){
wrapper = shallow(<SelectContainer/>);
})
it('should trigger change event when policy dropdown is clicked', function(){
const selectElt = wrapper.find('#foodSelector');
selectElt.simulate('change', { preventDefault() {}, target: { value: 'TestingValue' } });
selectElt.simulate('change', { preventDefault() {}, target: { value: 'select' } });
});
});
I am getting an error as 'TypeError: Cannot set property 'value' of undefined', so can anyone help me on how to mock the document.getElementById?
I tried using sinon as below,
var selectElt = document.createElement('select');
selectElt.id = 'foodSelector';
var f = sinon.stub(document, 'getElementById').withArgs('foodSelector').returns(selectElt);
expect(f.value).to.be.equal('select');
Even then i am getting the same error as below,
TypeError: Cannot set property 'value' of undefined

Unit Test RxJS Observable.timer using typescript, karma and jasmine

Hi I'm relatively new to Angular2, Karma and Jasmine. Currently I'm using Angular 2 RC4 Jasmine 2.4.x
I have an Angular 2 service which periodically calls an http service like this:
getDataFromDb() { return Observable.timer(0, 2000).flatMap(() => {
return this.http.get(this.backendUrl)
.map(this.extractData)
.catch(this.handleError);
});
}
Now I want to test the functionality. For testing purposes I have just tested the "http.get" on a separate function without the Observable.timer by doing:
const mockHttpProvider = {
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}
}
describe('data.service test suite', () => {
var dataFromDbExpected: any;
beforeEachProviders(() => {
return [
DataService,
MockBackend,
BaseRequestOptions,
provide(Http, mockHttpProvider),
];
});
it('http call to obtain data',
inject(
[DataService, MockBackend],
fakeAsync((service: DataService, backend: MockBackend) => {
backend.connections.subscribe((connection: MockConnection) => {
dataFromDbExpected = 'myData';
let mockResponseBody: any = 'myData';
let response = new ResponseOptions({ body: mockResponseBody });
connection.mockRespond(new Response(response));
});
const parsedData$ = service.getDataFromDb()
.subscribe(response => {
console.log(response);
expect(response).toEqual(dataFromDbExpected);
});
})));
});
I obviously want to test the whole function with the Observable.timer. I think one might want to use the TestScheduler from the rxjs framework, but how can I tell to only repeat the timer function for x times? I couln't find any documentation using it in the typescript context.
Edit: I'm using rxjs 5 beta 6
Edit: Added working example for Angular 2.0.0 final release:
describe('when getData', () => {
let backend: MockBackend;
let service: MyService;
let fakeData: MyData[];
let response: Response;
let scheduler: TestScheduler;
beforeEach(inject([Http, XHRBackend], (http: Http, be: MockBackend) => {
backend = be;
service = new MyService(http);
fakeData = [{myfake: 'data'}];
let options = new ResponseOptions({ status: 200, body: fakeData });
response = new Response(options);
scheduler = new TestScheduler((a, b) => expect(a).toEqual(b));
const originalTimer = Observable.timer;
spyOn(Observable, 'timer').and.callFake(function (initialDelay, dueTime) {
return originalTimer.call(this, initialDelay, dueTime, scheduler);
});
}));
it('Should do myTest', async(inject([], () => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
scheduler.schedule(() => {
service.getMyData().subscribe(
myData => {
expect(myData.length).toBe(3,
'should have expected ...');
});
}, 2000, null);
scheduler.flush();
})));
});
You need to inject the TestScheduler into the timer method inside a beforeEach part:
beforeEach(function() {
this.scheduler = new TestScheduler();
this.scheduler.maxFrames = 5000; // Define the max timespan of the scheduler
const originalTimer = Observable.timer;
spyOn(Observable, 'timer').and.callFake(function(initialDelay, dueTime) {
return originalTimer.call(this, initialDelay, dueTime, this.scheduler);
});
});
After that you have full control of the time with scheduleAbsolute:
this.scheduler.schedule(() => {
// should have been called once
// You can put your test code here
}, 1999, null);
this.scheduler.schedule(() => {
// should have been called twice
// You can put your test code here
}, 2000, null);
this.scheduler.schedule(() => {
// should have been called three times
// You can put your test code here
}, 4000, null);
this.scheduler.flush();
You need scheduler.flush() to start the TestScheduler.
edit: so if you want to only test it X times, use the schedule functions as often (and with the right absolute times in milliseconds) as you wish.
edit2: I added the missing scheduler start
edit3: I changed it so should be working with RxJs5
edit4: Add maxFrames setting since the default is 750ms and will prevent testing longer-running sequences.
I had issues with the TestScheduler() approach because the schedule() arrow function would never execute, so I found another path.
The Observable.timer function just returns an Observable, so I created one from scratch to give me complete control.
First, create a var for the observer:
let timerObserver: Observer<any>;
Now in the beforeEach() create the spy and have it return an Observable. Inside the Observable, save your instance to the timer:
beforeEach(() => {
spyOn(Observable, 'timer').and.returnValue(Observable.create(
(observer => {
timerObserver = observer;
})
));
});
In the test, just trigger the Observable:
it('Some Test',()=>{
// do stuff if needed
// trigger the fake timer using the Observer reference
timerObserver.next('');
timerObserver.complete();
expect(somethingToHappenAfterTimerCompletes).toHaveBeenCalled();
});
You can test Observable timers pretty easily with fakeAsync(). Here's a component that displays a countdown timer (using a momentJS duration):
timeout.component.ts
#Component({
selector: 'app-timeout-modal',
templateUrl: './timeout-modal.component.html'
})
export class TimeoutModalComponent implements OnInit {
countdownTimer: Observable<number>;
countdownSubscription: Subscription;
durationLeft = moment.duration(60000); // millis - 60 seconds
ngOnInit() {
this.countdownTimer = Observable.timer(0, 1000);
this.countdownSubscription = this.countdownTimer
.do(() => this.durationLeft.subtract(1, 's'))
.takeWhile(seconds => this.durationLeft.asSeconds() >= 0)
.subscribe(() => {
if (this.durationLeft.asSeconds() === 0) {
this.logout();
}
});
}
}
timeout.component.spec.ts
beforeEach(async(() => {
...
}));
beforeEach(() => {
fixture = TestBed.createComponent(TimeoutModalComponent);
component = fixture.componentInstance;
});
it('should show a count down', fakeAsync(() => {
fixture.detectChanges();
expect(component.durationLeft.asSeconds()).toEqual(60);
tick(1000);
fixture.detectChanges();
expect(component.durationLeft.asSeconds()).toEqual(59);
component.countdownSubscription.unsubscribe();
}));
I was struggling with this for a while also. Since apparently a lot has changed in the frameworks since this question was asked, I thought maybe someone would be helped by my solution. My project uses rxjs 5, jasmine 2.8 and angular 5.
In my component a timer was used to call a http-get function in a service every minute. My problem was that when using fakeAsync zone the (stubbed) get function was never called and I received the error: "Error: 1 periodic timer(s) still in the queue.".
The error is showing up because the timer keeps firing and isn't stopped at the end of the test. This can be resolved by adding "discardPeriodicTasks();" to the end of the test, which causes the timer to stop. Tick(); can be used to fake to passage of time untill a next call. I used a spy on my get-function in my service to see if it worked:
it(
'should call getTickets from service every .. ms as defined in refreshTime',
fakeAsync(() => {
fixture.detectChanges();
tick();
expect(getTicketsSpy).toHaveBeenCalledTimes(1);
// let 2 * refreshtime pass
tick(2 * component.refreshTime);
expect(getTicketsSpy).toHaveBeenCalledTimes(3);
discardPeriodicTasks();
})
);
The refreshTime is the parameter that I used in the timer. I hope this prevents someone from spending half a day trying to figure this out.