Ember acceptance tests fail when ran all at once - unit-testing

I have about 5 acceptance tests which all pass when ran individually. When I want to run all of my tests, the acceptance tests will fail, except for the first one that was ran. All acceptance tests except for the first one will simply not render the application, thus all failing. I've checked to see if there are any pending waiters and if the state is settled and I have not been able to find anything where i can tell it's not. I've also added the waitFor declarator to certain async parts of my code to make sure that everything is resolved. All requests are also mocked with MSW. I start the mock service worker on every test, so that should definitely be running.
These are the current versions of the related dependencies:
"#ember/test-helpers": "^2.8.1", "qunit": "^2.17.2", "qunit-dom": "^1.6.0", "ember-qunit": "^5.1.5", "ember-source": "~3.28.8", "ember-cli": "~3.28.5"
Here is an example from one of my acceptance tests. This file contains three tests. When running all the tests, the first one will act normally and all the following ones will not render, thus failing.
import { visit, waitFor, click, clearRender } from '#ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import backstop from 'ember-backstop/test-support/backstop';
import { authenticateSession } from 'ember-simple-auth/test-support';
import { worker } from 'atlas-mocking/browser';
import { getSettledState } from '#ember/test-helpers/settled';
import { getPendingWaiterState } from '#ember/test-waiters';
module('Acceptance | Oportunity test', function (hooks) {
setupApplicationTest(hooks);
test('checking if basic values get loaded into detail page', async function (assert) {
worker.start();
authenticateSession({
authToken: '12345',
otherData: 'some-data',
});
await visit('/opportunity/index');
await waitFor('[data-test="modelRouteTable"]');
assert.dom('[data-test="modelRouteTable"]').exists();
await visit('/opportunity/258234/view');
//check if detailscreen of view page has been rendered
await waitFor('[data-test="opportunityDetailSection"]');
await backstop(assert);
assert.dom('[data-test="name"]').exists();
assert.dom('[data-test="opportunityStage"]').exists();
assert.dom('[data-test="opportunityType"]').exists();
assert.dom('[data-test="closeDate"]').exists();
assert.dom('[data-test="currency"]').exists();
assert.dom('[data-test="company"]').exists();
assert.dom('[data-test="contact"]').exists();
assert.dom('[data-test="externalReference"]').exists();
assert.dom('[data-test="owner"]').exists();
assert.dom('[data-test="organization"]').exists();
assert.dom('[data-test="salesOrder"]').exists();
assert.dom('[data-test="campaign"]').exists();
assert.dom('[data-test="groupRequestDetail"]').exists();
assert.dom('[data-test="directionType"]').exists();
assert.dom('[data-test="departureDate"]').exists();
assert.dom('[data-test="returnDate"]').exists();
assert.dom('[data-test="directFlightsPreferred"]').exists();
assert.dom('[data-test="requestNumber"]').exists();
assert.dom('[data-test="country"]').exists();
assert.dom('[data-test="extraFlightInformationDetail"]').exists();
assert.dom('[data-test="passengerNames"]').exists();
assert.dom('[data-test="budgetPerPerson"]').exists();
assert.dom('[data-test="luggage"]').exists();
assert.dom('[data-test="requestedAirline"]').exists();
assert.dom('[data-test="groupType"]').exists();
assert.dom('[data-test="commentsDetail"]').exists();
assert.dom('[data-test="comments"]').exists();
assert.dom('[data-test="internalComments"]').exists();
console.log(getSettledState());
console.log(getPendingWaiterState());
await clearRender();
});
test('checking if correct values get added into edit view', async function (assert) {
worker.start();
authenticateSession({
authToken: '12345',
otherData: 'some-data',
});
await visit('/opportunity/index');
await waitFor('[data-test="modelRouteTable"]');
assert.dom('[data-test="modelRouteTable"]').exists();
await visit('/opportunity/258234/view');
await waitFor('[data-test="opportunityDetailSection"]');
await click('[data-test="editButton"]');
await waitFor('[data-test="opportunityEdit"]');
//check if editscreen of detail page has been rendered with correct values
assert.dom('[data-test="nameInput"] input').hasAnyValue();
assert.dom('[data-test="opportunityStageInput"] select').hasAnyValue();
assert.dom('[data-test="opportunityTypeInput"] select').hasAnyValue();
assert.dom('[data-test="closeDateInput"] input').hasAnyValue();
assert.dom('[data-test="currencyInput"] select').hasAnyValue();
assert.dom('[data-test="companyInput"] .name').hasAnyText();
assert.dom('[data-test="contactInput"] .name').hasAnyText();
assert.dom('[data-test="externalReferenceInput"] input').hasAnyValue();
assert.dom('[data-test="ownerInput"] .name').hasAnyText();
assert.dom('[data-test="organizationInput"] select').hasAnyValue();
assert.dom('[data-test="directionTypeInput"] select').hasAnyValue();
assert.dom('[data-test="departureDateInput"] input').hasAnyValue();
assert.dom('[data-test="returnDateInput"] input').hasAnyValue();
assert.dom('[data-test="directFlightsPreferredInput"] input').hasNoValue();
assert.dom('[data-test="requestNumberInput"] input').hasNoValue();
assert.dom('[data-test="countryInput"] .name').doesNotExist();
assert.dom('[data-test="passengerNamesInput"] select').hasAnyValue();
assert.dom('[data-test="budgetPerPersonInput"] input').hasNoValue();
assert.dom('[data-test="luggageInput"] select').hasAnyValue();
assert.dom('[data-test="requestedAirlineInput"] .name').doesNotExist();
assert.dom('[data-test="groupTypeInput"] select').hasAnyValue();
assert.dom('[data-test="commentsInput"] textarea').hasNoValue();
assert.dom('[data-test="internalCommentsInput"] textarea').hasNoValue();
await click('[data-test="saveButton"]');
console.log(getSettledState());
console.log(getPendingWaiterState());
await clearRender();
});
test('checking if new opportunity renders correctly', async function (assert) {
worker.start();
authenticateSession({
authToken: '12345',
otherData: 'some-data',
});
await visit('/opportunity/index');
await waitFor('[data-test="modelRouteTable"]');
assert.dom('[data-test="modelRouteTable"]').exists();
await click('[data-test="addButton"]');
assert.dom('[data-test="nameInput"] input').hasNoValue();
assert.dom('[data-test="opportunityStageInput"] select').hasAnyValue();
assert.dom('[data-test="opportunityTypeInput"] select').hasNoValue();
assert.dom('[data-test="closeDateInput"] input').hasAnyValue();
assert.dom('[data-test="currencyInput"] select').hasAnyValue();
assert.dom('[data-test="companyInput"] .name').doesNotExist();
assert.dom('[data-test="contactInput"] .name').doesNotExist();
assert.dom('[data-test="externalReferenceInput"] input').hasNoValue();
assert.dom('[data-test="ownerInput"] .name').hasAnyText();
assert.dom('[data-test="organizationInput"] select').hasNoValue();
assert.dom('[data-test="salesOrderInput"] .name').doesNotExist();
assert.dom('[data-test="campaignInput"] .name').doesNotExist();
console.log(getSettledState());
console.log(getPendingWaiterState());
await clearRender();
});
});```

There was some leaking state caused by a mock websocket. It didn't show any leaking state in getPendingWaiters or getSettledState. By using getDebugInfo, I could see that there were still some unfinished counters though. After this i looked deeper into the code and found that still being the problem by commenting pieces of code.

Related

Is there any way to run ember acceptance test case in slow mode?

I have written some ember acceptance test cases. The thing is when I visit the URL http://localhost:4200/tests and filter module by Acceptance, the test cases are running lightning fast. Even though those were written in async await function.
I want to put a time delay for each line using ember run loop run.later or run.next. But that's not the solution.
Is there a way to add slow mode somewhere in the top of the application (or) Is there any test helpers which is already present? So I can see test cases running in action.
There's no "slow mode" for Ember testing, but you can use regular JavaScript methods to slow down your tests yourself. For example, below we've created a wait method that you can call in between each step of the test.
This question was featured on an episode of May I Ask a Question! You can watch the recording to see examples of how to use the code below plus how to use the debugger and pauseTest.
This code will wait one second before moving to the next line, anywhere you put await wait():
import { module, test } from 'qunit';
import { visit, currentURL } from '#ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { click, find } from '#ember/test-helpers';
function wait(timeout = 1000) {
return new Promise((resolve) => {
setTimeout(resolve, timeout);
});
}
module('Acceptance | slowdown', function(hooks) {
setupApplicationTest(hooks);
test('clicking the button reveals text', async function(assert) {
await visit('/');
await wait();
await click('.test-button-1');
await wait();
assert.equal(find('.some-message').innerText, 'first message');
await click('.test-button-2');
await wait();
assert.equal(find('.some-message').innerText, 'second message');
});
});
I also recommend using your browser's debugger to step through code at your own pace. It's really powerful and will probably help you more than slowing down tests, in most cases. this.pauseTest() is also very helpful.
There's also the possibility of using a JavaScript generator for some less-cluttered code, but if you use this, your tests won't exit properly, so it's a temporary approach:
import { module, test } from 'qunit';
import { visit, currentURL } from '#ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { click, find } from '#ember/test-helpers';
function slowTest(message, generatorFunction) {
test(message, async function(assert) {
let generator = generatorFunction(assert);
window.step = window.next = async () => {
generator.next();
await this.pauseTest();
}
window.finish = () => {
generator.return();
this.resumeTest();
}
await this.pauseTest();
});
}
module('Acceptance | slowdown', function(hooks) {
setupApplicationTest(hooks);
slowTest('clicking the button reveals text', function*(assert) {
yield visit('/');
yield click('.test-button-1');
assert.equal(find('.some-message').innerText, 'first message');
yield click('.test-button-2');
assert.equal(find('.some-message').innerText, 'second message');
});
});

Jest how to check async response

I'm new to Jest and React so this should be a very simple question to answer... I have an api-endpoint I'd like to check that I can hit. I picked Axios as a client to try this and created the following test:
describe('Api Tests', () => {
it('can perform an axios request', () => {
console.log('Here goes!');
const resp = axios.get('api-endpoint');
console.log(resp);
expect(resp).toBeDefined();
console.log('Done...');
});
});
Thankfully, the test passes, but with the following output:
PASS src\api\api.test.js
Api Tests
√ can perform an axios request (39ms)
console.log src\api\api.test.js:15
Here goes!
console.log src\api\api.test.js:23
Promise { <pending> }
console.log src\api\api.test.js:25
Done...
How do I test a simple request (WITHOUT MOCKING) so that I can get back a response that I can then interrogate?
Since axios.get returns a promise you should instruct Jest to wait for the response to return.
it('can perform an axios request', async () => {
const resp = await axios.get('api-endpoint');
expect(resp).toBeDefined();
});
or without async functions:
it('can perform an axios request', () => {
return expect(axios.get('api-endpoint')).resolves.toBeDefined();
});
I have nice and easy method , maybe you will like it too. Just keep it simple.
`import axios from 'axios';
axios.get('url goes here')
.then(response =>{console.log(response)})`
Maybe find helpful to you.

Ember; how to test behavior of a caught exception which wraps code in Ember.run

I am testing a service method that returns a promise; I want to verify that it catches an error, calls an error-reporting service, and then rethrows the error. I thought that since this error handler was a callback, I should run the catch code in Ember.run, but that is making my tests fail after upgrading from Ember 2.3 to Ember 2.13. Removing the Ember.run fixes the tests but I assume uses the autorun loop, which the guides discourage. What am I doing wrong?
service.js
// .... service boilerplate
doAsyncThing() {
return get(this, 'someService').postV2Request().catch((e) => {
// tests fail unless I remove Ember.run
Ember.run(() => {
let logError = new Error('foobar error');
this.reportError(logError);
throw(e);
});
});
}
// ... service boilerplate
test.js
test('doAsyncThing reports an error if there is one', function(assert) {
assert.expect(3);
let done = assert.async();
let deferred = RSVP.defer();
let apiService = mock({
postV2Request: sinon.stub().returns(deferred.promise)
});
let reportErrorStub = sinon.stub();
let service = this.subject({
apiService,
reportError: reportErrorStub
});
service.doAsyncThing('foo', 'bar', 'baz').catch(() => {
assert.ok(true, 'The exception is rethrown');
assert.ok(reportErrorStub.called, 'reportError was called');
assert.equal(reportErrorStub.args[0][0].message, 'foobar error', 'format of message is ok');
done();
});
deferred.reject();
});
As it turns out, promise callbacks are automatically wrapped in run loops by Ember, so my extra call to run was probably getting scheduled later. More details here https://github.com/emberjs/ember.js/issues/11469 but the TLDR was, the Ember.run was superflous and made Ember think the exception was uncaught due to specifics about Ember's testing mode.

Angular2 async unit testing with jasmine

I'm writing an angular2 app and having trouble understanding how to write tests for async code using jasmine. For whatever reason, I'm not seeing a lot of examples that seem terribly applicable to my situation.
I'm currently trying to test a service (not a component) that has a async dependency on another service. I'm not 100% sure at what point in the test it's valid to check the results of the async call. Can you just call expect() inside the async handler for your service?
service.foo()
.then((data) => {
//do I check the results in here?
expect(data).toEqual({ a: 1, b: 2 });
expect(mockDep.get).toHaveBeenCalled();
});
Here is the full test.
import { TestBed, inject } from '#angular/core/testing';
import { MyService } from './my.service.ts';
import { MyDependency } from './dependency.service.ts';
class MockDependency {
doSomething(): Promise<any> {
throw Error('not implemented');
};
}
describe('some tests', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MyService,
{
provide: MyDependency, useClass: MockDependency
}
]
});
});
});
it('should do something', inject([MyService, MyDependency], (service: MyService, mockDep: MyDependency) => {
spyOn(mockDep, 'doSomething').and.callFake(function () {
return Promise.resolve({ a: 1, b: 2 });
});
service.foo()
.then((data) => {
//do I check the results in here?
expect(data).toEqual({ a: 1, b: 2 });
expect(mockDep.get).toHaveBeenCalled();
});
}));
There's two aspects of dealing with async tests that you have to concern yourself about if you want to ensure your tests are actually reliable.
First, you have to ensure that if a result is retrieved asynchronously, that you wait until the result is available before you try to test for it.
Hence, if the asynchronous result is a promise, for eg, you can put your expect in your then handler, just as you indicated in your question.
The second issue that you have to concern yourself with is forcing your test itself to wait for your expectations to execute before giving a positive (or negative) result. If you don't deal with this, you can have cases where your expectation fails, but because your test did not wait for your asynchronous action to complete before finishing, the test reports a false positive.
There are several ways of 'making your test wait'.
The pure jasmine way is to pass a done handler into your it function. Then jasmine will wait until that done handler is called before considering the test complete.
eg.
it('tests an async action', (done) => {
asyncAction().then(result => {
expect(result).toEqual(true);
done();
});
});
However, angular's testing framework adds two other options to this. The first is easier to grasp if you are comfortable with async programming.
it('tests an async action', async(() => {
asyncAction().then(result => {
expect(result).toEqual(true);
});
}));
in this case, you basically wrap your test handler in an async function. This function will force the test to wait for any async results (eg promises, observables etc) to return a result, before allowing the test to complete.
The second method is to use fakeAsync, which allows you to hide the async nature of your test entirely.
it('tests an async action', fakeAsync(() => {
let myResult;
asyncAction().then(result => {
myResult = result;
});
tick(); <--- force all async actions to complete
expect(myResult).toEqual(true);
}));
fakeAsync hooks into all async functions and allows you to treat them as synchronous. You can use the tick() function to 'force your test to wait' for async tasks to complete before continuing.
(It's not quite doing that, but conceptually, you can think of it that way).
See the Angular docs to learn more

Angular2 - testing component with two "it" creates 'selector "#root0" did not match any elements error'

I am trying to create a simple component test, when I createAsync an element twice I get The selector "#root0" did not match any elements error. I assume it creates it the second time with #root1 but looks for #root0
it('should render',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(TestComponent)
.then((componentFixture) => {
componentFixture.detectChanges();
expect(true).toBeTruthy();
componentFixture.destroy();
}).catch((e) =>{
console.log(e);
});
})
);
it('should render',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(TestComponent)
.then((componentFixture) => {
componentFixture.detectChanges();
expect(true).toBeTruthy();
componentFixture.destroy();
}).catch((e) =>{
console.log(e);
});
})
);
If I run just one "it" test it works fine. the second one fails... I tried it with and without the componentFixture.destroy(); but no success...
To be clear - the tests passes, but the error shows up in the console.
Here is the complete error log:
LOG: BaseException{message: 'The selector "#root0" did not match any elements', stack: 'Error: The selector "#root0" did not match any elements
at new BaseException (http://localhost:9876/base/node_modules/angular2/bundles/angular2.dev.js?914563a3aa3b4999ed51fe88c1b6233d2f09e880:7070:21)
at DomRenderer.selectRootElement (http://localhost:9876/base/node_modules/angular2/bundles/angular2.dev.js?914563a3aa3b4999ed51fe88c1b6233d2f09e880:13643:15)
at HostViewFactory.viewFactory_HostTestComponent0 [as viewFactory] (viewFactory_HostTestComponent:72:18)
at AppViewManager_.createRootHostView (http://localhost:9876/base/node_modules/angular2/bundles/angular2.dev.js?914563a3aa3b4999ed51fe88c1b6233d2f09e880:9172:34)
at http://localhost:9876/base/node_modules/angular2/bundles/angular2.dev.js?914563a3aa3b4999ed51fe88c1b6233d2f09e880:12189:46
at M (http://localhost:9876/base/node_modules/systemjs/dist/system-polyfills.js?064ab212cfd9e125474ae3bbb600c366b31e79cb:4:8769)
at H (http://localhost:9876/base/node_modules/systemjs/dist/system-polyfills.js?064ab212cfd9e125474ae3bbb600c366b31e79cb:4:8401)
at R.when (http://localhost:9876/base/node_modules/systemjs/dist/system-polyfills.js?064ab212cfd9e125474ae3bbb600c366b31e79cb:4:12075)
at b.run (http://localhost:9876/base/node_modules/systemjs/dist/system-polyfills.js?064ab212cfd9e125474ae3bbb600c366b31e79cb:4:11111)
at t._drain (http://localhost:9876/base/node_modules/systemjs/dist/system-polyfills.js?064ab212cfd9e125474ae3bbb600c366b31e79cb:4:3029)
at drain (http://localhost:9876/base/node_modules/systemjs/dist/system-polyfills.js?064ab212cfd9e125474ae3bbb600c366b31e79cb:4:2683)
at MutationObserver.e (http://localhost:9876/base/node_modules/systemjs/dist/system-polyfills.js?064ab212cfd9e125474ae3bbb600c366b31e79cb:4:4604)
at Zone.run (http://localhost:9876/base/node_modules/angular2/bundles/angular2-polyfills.js?2a193e6e9bdd25760b711f1ce03caeac530e48c1:138:17)
at MutationObserver.zoneBoundFn (http://localhost:9876/base/node_modules/angular2/bundles/angular2-polyfills.js?2a1
This is a known issue https://github.com/angular/angular/issues/6483 (dup of https://github.com/angular/angular/issues/5662) when templateUrl is used in components.
Update
This is fixed in Angular 2.0.0-beta.3
See https://github.com/angular/angular/issues/6483#issuecomment-179557485 for more details
Basically, what I had to do:
Manually add typings for jasmine with tsd install jasmine -so and add ///<reference... in the test files;
Add this in my imports:
import {setBaseTestProviders} from 'angular2/testing';
import {
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
} from 'angular2/platform/testing/browser';
Add this before my Component tests:
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);
An update to the update:
Beta.3 did fix the problem as Günter Zöchbauer mentioned, we can now use injectAsync that didn't work before.
Also I suggest to use this:
import {setBaseTestProviders} from 'angular2/testing';
import {getTestInjector} from 'angular2/testing';
import {TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS
} from 'angular2/platform/testing/browser';
if (getTestInjector().platformProviders.length === 0 || getTestInjector().applicationProviders.length === 0) {
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);
}
otherwise you will get an error when loading your BaseTestProviders the second time.