Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Routes Testing (mostly for endpoints) #983

Closed
cristovao-trevisan opened this issue Apr 12, 2021 · 2 comments
Closed

Routes Testing (mostly for endpoints) #983

cristovao-trevisan opened this issue Apr 12, 2021 · 2 comments

Comments

@cristovao-trevisan
Copy link
Contributor

Context

I'm building an app that use endpoints routes as a full backend. I'm my specific case it have a mongodb database, which cannot be accessed from outside (aka frontend) for obvious security reasons. An important part of backend is testing, so I found a way to test it, and decide to share/discuss how to do it, as others will probably want to do the same.

I saw #19 is open about how to run tests, but it discuss more about unity testing and not how to retrieve routes, so I opened a new issue.

How I made it work

Both jest and uvu have the option use setup and teardown hooks (jest global setup, uvu context), so I decided to spin up the dev server using it. At first I thought about calling the function that does it in svelte/kit, but gave up since it's tightly couple with the cli code.

I ended up with the following configuration (I'm using jest):

.jest/kit.cjs

const { exec } = require('child_process');
const onExit = require("signal-exit");

process.env.TEST_PORT = 3031
process.env.TEST_URL = `http://localhost:${process.env.TEST_PORT}`;

let p;
async function start() {
  if (p) return;
  p = exec(`TEST_ENV=1 npm run dev -- --port ${process.env.TEST_PORT}`);
  await new Promise((resolve) => {
    p.stdout.on('data', data => {
      if (str.includes(process.env.TEST_URL)) resolve();
    });
  });
  onExit(() => {
    stop();
    process.exit();
  });
}

function stop() {
  if (p) { 
    p.kill();
    p = null;
  }
}

module.exports = { start, stop };

.jest/setup.cjs

const kit = require('./kit.cjs');

module.exports = kit.start;

.jest/teardown.cjs

const kit = require('./kit.cjs');

module.exports = function(opts) {
  if (!opts.watch && !opts.watchAll) {
    kit.stop();
  }
};

Improvements

I used a child process because the code that spins up the server is currently coupled with the cli, it would look much nicer if there was a function to do it instead.

Discussion

I understand that this is not the best idea, the ideal would be to render routes (ou endpoints) programatically. I look at the code for a way to do this, but vite doesn't seem to support or like it. As the routes are based on the file system, it is a ton of work to do this, so I think it's reasonable to spin up a server instead.

Also, I decided to spin up the dev server (npm run dev) because it's much more useful when running tests in watch mode.

A glimpse of what the test looks like:

test('request some api endpoint', async () => {
  // set answer
  let body = JSON.stringify({ param: 'test' });
  let session = await testSession();
  let headers = { 'Content-Type': 'application/json', ...session };
  let res = await fetch(`${process.env.TEST_URL}/some-api.json`, { method: 'PUT', body, headers });
  expect(res.status).toBe(200);
  //...
});
@Rich-Harris
Copy link
Member

It is in fact possible to call endpoints programmatically, once you've built the app — the .svelte-kit/output/server directory contains the necessary files:

  • index.js — the server entry point
  • manifest.js — a manifest file that contains data about your app, and dynamic imports for each of your routes (not exactly the same as the manifest generated inside adapters, which takes account of prerendering and server-side code-splitting, but functionally the same)

Try this, for example:

import { installFetch } from '@sveltejs/kit/install-fetch';
import { Server } from './.svelte-kit/output/server/index.js';
import { manifest } from './.svelte-kit/output/server/manifest.js';

installFetch(); // we're in Node, so we need to polyfill `fetch` and `Request` etc

const server = new Server(manifest);

const request = new Request('https://example.com/my-endpoint');
const response = await server.respond(request);

console.log(await response.json());

These files aren't exactly documented, and could change without notice. In theory we could promote them to public API so that they have semver guarantees, though to be honest I think it's always going to be better to test it via a server (whether npm run dev or npm run build && npm run preview) so that probably won't happen.

Anyway, I'm going to close this issue because there's no action needed here, but thanks for sharing your experience.

@danielres
Copy link

I found a simpler way to accomplish this using vite-test-utils.

I made a little demo app with some explanations:

https://github.com/danielres/how-to-test-sveltekit-api-routes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants