I'm using promises to wrap asynchronous (Mongo) DB ops at the end of an (expressJS) route.
I want to try and figure out how to test the following code.
userService
userService.findOne = function (id) {
var deferred = q.defer();
User.findOne({"_id" : id})
.exec(function (error, user) {
if (error) {
deferred.reject(error);
} else {
deferred.resolve(user);
}
});
return deferred.promise;
};
userRoute
var user = function (req, res) {
var userId = req.params.id
, userService = req.load("userService");
// custom middleware that enables me to inject mocks
return userService.findOne(id)
.then(function (user) {
console.log("called then");
res.json({
msg: "foo"
});
}).catch(function (error) {
console.log("called catch");
res.json({
error: error
});
}).done();
};
Here's an attempt to test the above with mocha
userTest
it("when resolved", function (done) {
var jsonSpy = sinon.spy(httpMock.res, "json")
, httpMock = require("/path/to/mock/http/object")
, serviceMock = require("/path/to/mock/service"),
, deferred = q.defer()
, findStub = sinon.stub(serviceMock, "findOne")
.returns(deferred.promise)
, loadStub = sinon.stub(httpMock.req, "load")
.returns(serviceMock),
retPromise;
// trigger route
routes.user(httpMock.req, httpMock.res);
// force promise to resolve?
deferred.resolve();
expect(jsonSpy.called).to.be.true; // fails
// chai as promised
retPromise = findStub.returnValues[0];
expect(retPromise).to.be.fulfilled; // passes
});
the http mock is just an empty object with no-ops where expressJS would normally start rendering stuff. I've added some logging inside those no-ops to get an idea on how this is hanging together.
This isn't really working out. I want to verify how the whole is integrated, to establish some sort of regression suite - but I've effectively mocked it to smithereens and I'm just testing my mocks (not entirely successfully at that).
I'm also noticing that the console logs inside my http mocks triggered by then and catch are firing twice - but the jsonSpy that is invoked inside the actual code (verified by logging out the sinon spy within the userRoute code) is not called in test.
Has anyone got some advice on integration testing strategies for express apps backed by Mongo?
It looks to me like you're not giving your promise an opportunity to fire before you check if the result has been called. You need to wait asynchronously for userService.findOne()'s promise chain to complete before jsonSpy.called will be set. Try this instead:
// start of code as normal
q.when(
routes.user(httpMock.req, httpMock.res),
function() { expect(jsonSpy.called).to.be.true; }
);
deferred.resolve();
// rest of code as normal
That should chain off the routes.user() promise and pass as expected.
One word of caution: I'm not familiar with your framework, so I don't know if it will wait patiently for all async events to go off. If it's giving you problems calling back into your defer chain, you may want to try nodeunit instead, which handles async tests very well (IMO).
Related
I am a beginner using jest to test a node/express app with mongo database.
I am getting an issue where different tests are failing each time I run the tests and sometimes they all pass/all fail. I think it is because of a time-out or things not happening in the right order because I'm getting this error:
MongoPoolClosedError: Attempted to check out a connection from closed connection pool
a) can you let me know if you think I'm on the right track?
b) if so, is the solution to make this into an async function and how can I do that? (I have tried making it into async await, using .then and also putting the code that clears the database collections into the test files instead of the helper and have had no success so far.
beforeAll( (done) => {
mongoose.connect("mongodb://127.0.0.1/jobBuddy_test", {
useNewUrlParser: true,
useUnifiedTopology: true,
})
var db = mongoose.connection;
const users = db.collection('users')
users.deleteMany({})
const applications = db.collection('applications')
applications.deleteMany({})
db.on("error", console.error.bind(console, "MongoDB connection error:"));
db.on("open", function () {
done();
});
});
afterAll(function (done) {
mongoose.connection.close(true, function () {
done();
});
});
I'm having trouble figuring out how to drive tests with data fetched from a request. I've read the documentation here: https://testcafe.io/documentation/402804/recipes/best-practices/create-data-driven-tests, and all examples use static json file data available at compile time.
I can't fetch the data in fixture.before hook, because it will only be available inside of the test context, but I need to access the data outside of the test context for iteration, such that the test is inside of a for loop.
I've tried this solution: https://github.com/DevExpress/testcafe/issues/1948, however this fails with testcafe ERROR No tests found in the specified source files. Ensure the sources contain the 'fixture' and 'test' directives., even when I use the flag disable-test-syntax-validation and .run({ disableTestSyntaxValidation: true }); option.
I am looking for suggestions and workarounds so that I can await some data, then run my tests. Even if Testcafe doesn't explicitly support something like this, I figure there must be some workaround... Thanks in advance.
Edit:
file-a.ts
export function tSteps(...args) {
// some setup
const testcase = args[args.length - 1];
const testCtx = test(name, async t => {
...
});
return testCtx;
}
----
file-b.ts
export const parameterizedTest = <T>(..., testcase: (scenario: T) => TestFn) => {
// some setup...
// I have also tried awaiting rows data here, which does not work
// because tests are not discoverable at compile time
...
const scenarios: T[] = rows.map(row => {
...
});
scenarios.forEach((scenario, idx) => {
return testcase(scenario).meta({
some metadata
});
});
};
----
tests.ts
fixture(...).before(async () => {
// can't get the data i need here because it needs to be available outside of the fixture context
})
parameterizedTest<MyInterface>(some params, (scenario: MyInterface) => {
return tSteps('my test',
async f => {
// some setup
// test code goes here which uses scenario.attributex, scenario.attributey, etc.
}
).meta(...);
}
);
In v1.0.0 and later, TestCafe does not validate test syntax. Please specify the TestCafe version that you use when you see the validation error.
Unfortunately, we cannot use pseudo-code to reproduce the issue you encountered. Please share some code that we could run to see the problematic behavior.
Generally speaking, TestCafe allows you to fetch data asynchronously and then spawn tests based on the received values. For instance, the following code works fine for me with TestCafe 1.18.3:
import { fixture, test } from 'testcafe';
import fetch from './node-fetch-mock';
(async () => {
const testData = await fetch();
testData
.map(n => () => {
fixture `Fixture ${n}`
.page `https://google.com`;
test(`Test ${n}`, async t => {
await t.expect(true).ok();
});
})
.map(async test => { await test(); });
})();
node-fetch-mock.js
export default async function fetch() {
return [1, 2, 3, 4, 5];
}
The only caveat is that I have to import fixture and test explicitly because I call them from callbacks.
Could you please provide us with any test code snippet that demonstrates the problem? We need to correctly understand the cause of the problem and reproduce it on our side.
I am trying to build an auto complete component and want to make it cancel unresolved requests to the server while they type.
I can find no documentation around this in the documentation for HttpClient. It mentions it IS cancellable (unlike fetch) but not how. https://aurelia.io/docs/plugins/http-services
Currently I have this which I cobbled together quite blindly, unsurprisingly it doesn't even abort the requests:
async searchTermChanged(newValue, oldValue) {
if (newValue.length < 3)
return;
if (this.promises.length) {
this.promises.forEach(x => x.abort());
//should probably remove too
}
var promise = this.httpClient.post(this.endpoint, { SearchTerm: newValue });
this.promises.push(promise);
var data = await promise;
var response = JSON.parse(data.response);
this.results = response;
}
}
Where can I find out more information on how to make cancellable requests? My google-fu is failing me.
Looks like you can do this:
this.client["pendingRequests"].forEach(request => {
request.abort();
});
I am having to do ["pendingRequests"] as I'm using TypeScript and the array does not seem to be within the definition.
Note: I am also using a scoped HttpClient per autocomplete, so that when it cancels all previous requests it will not accidentally cancel something else that the app is requesting.
I can't seem to get the before Hook to be asynchronous.
I've used a before hook to start it up, which it does, but I'm trying to pick up when it fails, so that I can fail/skip the whole block (since there is no server to test with).
I've used the [async] before hook all throughout my code, but in this case it's not waiting for my done to be fired.
Basically, got a bunch of MochaJS tests, and for one block I need to start up a Web Server so that I can make Requests to it to test the RESTful API. I want to make sure that it started successfully, hence the async method.
describe('RESTful Tests', function(){
// Start up the whole express server for these API tests
before(function(done) {
try {
server.on('error', (e) => {
console.error("HERE");
done();
});
listener = server.listen(port, function() {
console.log("Server Started");
done();
});
console.error("AFTER LISTEN");
} catch(err) {
console.error("ON Caught");
done();
}
console.error("At End!!");
});
This runs, and shows as an 1) "before all" hook in the test, but it does not wait for the done to be called. I'm only getting the output...
RESTful Tests
AFTER LISTEN
At End!!
1) "before all" hook
None of the other items show up, but I'll get an exception (expected as I've blocked the port) about the EADDRINUSE. I'm really struggling to find a way to catch this.
I understand that it's in a different (kinda) "thread", so I wouldn't pick up the error, but still.... why is the asynchronous done method not being obeyed?
Seems some of the documentation out there is a bit misleading...
The Node/ExpressJS documentation state to put the .on('error' method on the server object. Seems in this case it needed to be on the listener object!
So my final [working] code looks like this...
listener = server.listen(port, function() {
done();
}).on('error', (function(e) {
this.skip(e.code);
done();
}).bind(this));
Yay!!! :)
I try to create e2e tests with karma and jasmine with yeoman. In my karma-e2e.conf.js I add jasmine:
files = [
JASMINE,
JASMINE_ADAPTER,
ANGULAR_SCENARIO,
ANGULAR_SCENARIO_ADAPTER,
'test/e2e/**/*.js'
];
A need async testing so I need to use runs, waits, waitsFor (https://github.com/pivotal/jasmine/wiki/Asynchronous-specs)
But if I try to use it:
it('test', function () {
runs(function () {
...
});
});
Scenatio test runner returns this:
TypeError: Cannot call method 'runs' of null
at runs (http://localhost:8080/adapter/lib/jasmine.js:562:32)
at Object.<anonymous> (http://localhost:8080/base/test/e2e/eduUser.js:42:3)
at Object.angular.scenario.SpecRunner.run (http://localhost:8080/adapter/lib/angular-scenario.js:27057:15)
at Object.run (http://localhost:8080/adapter/lib/angular-scenario.js:10169:18)
I don't know where the problem is. Can you help me please?
Angular e2e tests with Karma don't and can't use the JASMINE adapter. Instead you have the ANGULAR_SCENARIO_ADAPTER which has a similar feel to writing Jasmine tests.
All commands in the adapter's API are asynchronous anyway. For example element('#nav-items').count() doesn't return a number, it returns a Future object. Future objects are placed in a queue and executed asynchronously as the runner progresses. To quote the API docs:
expect(future).{matcher}:
[...] All API statements return a future object, which get a value assigned after they are executed.
If you need to run your own asynchronous test code, you can extend the adapter's DSL, this is easier than it might sound. The idea is that you return your own Future which can be evaluated by a matcher such as toBe(). There are some examples on how to do this in the e2e-tests.js Gist from Vojta. Just remember to call done(null, myRetrunValue); when your test code is successful (myRetrunValue is the value evaluated by your matcher). Or done('Your own error message'); if you want the test to fail.
UPDATE: In response to question below. To simulate a login, first add a function called login to the dsl:
angular.scenario.dsl('login', function() {
return function(selector) {
// #param {DOMWindow} appWindow The window object of the iframe (the application)
// #param {jQuery} $document jQuery wrapped document of the application
// #param {function(error, value)} done Callback that should be called when done
// (will basically call the next item in the queuue)
return this.addFutureAction('Logging in', function(appWindow, $document, done) {
// You can do normal jQuery/jqLite stuff here on $document, just call done() when your asynchronous tasks have completed
// Create some kind of listener to handle when your login is complete
$document.one('loginComplete', function(e){
done(null, true);
}).one('loginError', function(e){
done('Login error', false);
});
// Simulate the button click
var loginButton = $document.find(selector || 'button.login');
loginButton.click();
})
};
});
And then call:
beforeEach( function()
{
expect( login('button.login') ).toBeTruthy();
});