refactor(integration): http - integration tests with
MODIFY
packages/@integration/package.json
packages/@integration/package.json
"@marblejs/middleware-logger": "^3.1.1",
"@marblejs/middleware-multipart": "^3.1.1",
"@marblejs/websockets": "^3.1.1",
+ "@marblejs/testing": "^3.1.1",
"@types/node": "^10.1.2",
"fp-ts": "^2.4.3",
"rxjs": "^6.5.3",

MODIFY
packages/@integration/src/http/index.ts
packages/@integration/src/http/index.ts
@@ -6,7 +6,7 @@ import { bodyParser$ } from '@marblejs/middleware-body';
import { api$ } from './effects/api.effects';
import { cors$ } from './middlewares/cors.middleware';
-const listener = httpListener({
+export const listener = httpListener({
middlewares: [
logger$({ silent: isTestEnv() }),
bodyParser$(),

MODIFY
packages/@integration/test/http.integration.spec.ts
packages/@integration/test/http.integration.spec.ts
-import * as request from 'supertest';
+import { pipe } from 'fp-ts/lib/pipeable';
import { HttpStatus } from '@marblejs/core';
import { ContentType } from '@marblejs/core/dist/+internal/http';
-import { createHttpServerTestBed } from '@marblejs/core/dist/+internal/testing';
-import { server } from '../src/http';
+import { createHttpTestBed, createTestBedSetup, TestBedSetup, HttpTestBed } from '@marblejs/testing';
+import { listener } from '../src/http';
+
+const useTestBedSetup = createTestBedSetup({
+ testBed: createHttpTestBed({ listener }),
+});
describe('API integration', () => {
- const httpTestBed = createHttpServerTestBed(server);
-
- test('POST returns 404 when route not found: /', async () =>
- request(httpTestBed.getInstance())
- .post('/')
- .expect(404));
-
- test('GET returns status 200: /api/v1', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1')
- .expect(200, '"API version: v1"'));
-
- test('GET returns status 200: /api/v2', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v2')
- .expect(200, '"API version: v2"'));
-
- test('GET returns status 400: /api/v3 if provided version is not supported', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v3')
- .expect(400, {
- error: {
+ let testBedSetup: TestBedSetup<HttpTestBed>;
+
+ beforeEach(async () => {
+ testBedSetup = await useTestBedSetup();
+ });
+
+ afterEach(async () => {
+ return testBedSetup.cleanup();
+ });
+
+ test('POST "/" returns 404 when route not found', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('POST'),
+ testBed.withPath('/'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(404);
+ expect(response.body.error).toEqual({
+ message: 'Route not found',
+ status: 404,
+ });
+ });
+
+ test('GET "/api/v1" returns status 200', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.body).toEqual('API version: v1');
+ });
+
+ test('GET "/api/v2" returns status 200', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v2'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.body).toEqual('API version: v2');
+ });
+
+ test('GET "/api/v3" returns status 400 if provided version is not supported', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v3'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(400);
+ expect(response.body.error).toEqual({
status: 400,
message: 'Validation error',
data: [{
@@ -39,109 +84,210 @@ describe('API integration', () => {
got: '"v3"'
}],
context: 'params'
- }
- }));
-
- test('GET: /api/v1/foo triggers 404 effect', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/foo')
- .expect(404, {
- error: {
+ });
+ });
+
+ test('GET "/api/v1/foo" triggers 404 effect', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/foo'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(404);
+ expect(response.body.error).toEqual({
message: 'Route not found',
status: 404,
- }
- }));
-
- test('GET returns error response: /api/v1/error', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/error')
- .expect(HttpStatus.NOT_IMPLEMENTED, {
- error: {
- status: HttpStatus.NOT_IMPLEMENTED,
- data: { reason: 'Not implemented' },
+ });
+ });
+
+ test('GET "/api/v1/error" returns error response', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/error'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(HttpStatus.NOT_IMPLEMENTED);
+ expect(response.body.error).toEqual({
message: 'Route not implemented',
- }
- }));
-
- test('GET returns collection: /api/v1/user', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/user')
- .set('Authorization', 'Bearer FAKE')
- .expect(200)
- .then(({ body }) => {
- expect(body).toHaveLength(3);
- }));
-
- test(`GET returns 200 with Content-Type=application/json: /api/v1/user?email=test%40test.com`, async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/user?email=test%40test.com')
- .set('Authorization', 'Bearer FAKE')
- .expect(200)
- .then(({ header }) => {
- expect(header['content-type']).toBe(ContentType.APPLICATION_JSON);
- }));
-
- test('GET returns single object: /api/v1/user/10', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/user/10')
- .set('Authorization', 'Bearer FAKE')
- .expect(200)
- .then(({ body }) => {
- expect(body.id).toEqual('10');
- }));
-
- test('GET returns 404 not found: /api/v1/user/0', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/user/0')
- .set('Authorization', 'Bearer FAKE')
- .expect(404, { error: { status: 404, message: 'User does not exist' } }));
-
- test('GET returns 401 if not authorized: /api/v1/user/10', async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/user/10')
- .expect(401, { error: { status: 401, message: 'Unauthorized' } }));
-
- test('parses POST body and returns echo for secured route: /api/v1/user', async () =>
- request(httpTestBed.getInstance())
- .post('/api/v1/user')
- .set('Authorization', 'Bearer FAKE')
- .send({ user: { id: 'test_id' } })
- .expect(200, { data: { user: { id: 'test_id' } } }));
-
- test(`parses POST ${ContentType.APPLICATION_X_WWW_FORM_URLENCODED} body and echoes back for secured route: /api/v1/user`, async () =>
- request(httpTestBed.getInstance())
- .post('/api/v1/user')
- .set('Authorization', 'Bearer FAKE')
- .set('Content-Type', ContentType.APPLICATION_X_WWW_FORM_URLENCODED)
- .send({ user: { id: 'test_id' } })
- .expect(200, { data: { user: { id: 'test_id' } } }));
-
- test(`returns static file as ${ContentType.TEXT_HTML}: /api/v1/static/index.html`, async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/static/index.html')
- .expect('Content-Type', ContentType.TEXT_HTML)
- .then(res => expect(res.text).toContain('<h1>Test</h1>')));
-
- test(`returns static file as ${ContentType.IMAGE_PNG}: /api/v1/static/img/flow.png`, async () =>
- request(httpTestBed.getInstance())
- .get('/api/v1/static/img/flow.png')
- .expect('Content-Type', ContentType.IMAGE_PNG));
-
- test(`OPTIONS returns 204`, async () =>
- request(httpTestBed.getInstance())
- .options('/api/v2')
- .set('origin', 'fake-origin')
- .expect('Access-Control-Allow-Methods', 'HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS')
- .expect('Access-Control-Allow-Origin', 'fake-origin')
- .expect('Access-Control-Allow-Headers', 'Authorization, X-Header')
- .expect('Access-Control-Max-Age', '36000')
- .expect('Access-Control-Allow-Credentials', 'true')
- .expect(204));
-
- test(`GET returns 200`, async () =>
- request(httpTestBed.getInstance())
- .get('/api/v2')
- .set('origin', 'fake-origin')
- .expect('Access-Control-Allow-Origin', 'fake-origin')
- .expect(200));
+ data: { reason: 'Not implemented' },
+ status: HttpStatus.NOT_IMPLEMENTED,
+ });
+ });
+
+ test('GET "/api/v1/user" returns collection', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/user'),
+ testBed.withHeaders({ 'Authorization': 'Bearer FAKE' }),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.body).toHaveLength(3);
+ });
+
+ test(`GET "/api/v1/user?email=test%40test.com" returns 200 with "Content-Type": "application/json" `, async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/user?email=test%40test.com'),
+ testBed.withHeaders({ 'Authorization': 'Bearer FAKE' }),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.headers['content-type']).toBe(ContentType.APPLICATION_JSON);
+ });
+
+ test(`GET "/api/v1/user/10" returns single object`, async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/user/10'),
+ testBed.withHeaders({ 'Authorization': 'Bearer FAKE' }),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.body.id).toEqual('10');
+ });
+
+ test('GET "/api/v1/user/0" returns 404 not found', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/user/0'),
+ testBed.withHeaders({ 'Authorization': 'Bearer FAKE' }),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(404);
+ expect(response.body.error).toEqual({
+ status: 404,
+ message: 'User does not exist',
+ });
+ });
+
+ test('GET "/api/v1/user/10" returns 401 if not authorized', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/user/0'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(401);
+ expect(response.body.error).toEqual({
+ status: 401,
+ message: 'Unauthorized',
+ });
+ });
+
+ test('POST "/api/v1/user" parses body and returns echo for secured route', async () => {
+ const testBed = await testBedSetup.useTestBed();
+ const data = { user: { id: 'test_id' } };
+
+ const response = await pipe(
+ testBed.req('POST'),
+ testBed.withPath('/api/v1/user'),
+ testBed.withHeaders({ 'Authorization': 'Bearer FAKE' }),
+ testBed.withBody(data),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.body).toEqual({ data });
+ });
+
+ test(`POST "/api/v1/user" parses ${ContentType.APPLICATION_X_WWW_FORM_URLENCODED} body and echoes back for secured route`, async () => {
+ const testBed = await testBedSetup.useTestBed();
+ const data = { user: { id: 'test_id' } };
+
+ const response = await pipe(
+ testBed.req('POST'),
+ testBed.withPath('/api/v1/user'),
+ testBed.withHeaders({ 'Authorization': 'Bearer FAKE' }),
+ testBed.withHeaders({ 'Content-Type': ContentType.APPLICATION_X_WWW_FORM_URLENCODED }),
+ testBed.withBody(data),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.body).toEqual({ data });
+ });
+
+ describe('files', () => {
+ test(`GET "/api/v1/static/index.html" returns static file as "${ContentType.TEXT_HTML}"`, async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/static/index.html'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.headers['content-type']).toEqual(ContentType.TEXT_HTML);
+ expect(response.body).toContain('<h1>Test</h1>');
+ });
+
+ test(`GET "/api/v1/static/img/flow.png" returns static file as "${ContentType.IMAGE_PNG}"`, async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v1/static/img/flow.png'),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.headers['content-type']).toEqual(ContentType.IMAGE_PNG);
+ });
+ })
+
+ describe('CORS', () => {
+ test('OPTIONS "/api/v2" returns 204', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('OPTIONS'),
+ testBed.withPath('/api/v2'),
+ testBed.withHeaders({ 'Origin': 'fake-origin' }),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(204);
+ expect(response.headers['access-control-allow-methods']).toEqual('HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS');
+ expect(response.headers['access-control-allow-origin']).toEqual('fake-origin');
+ expect(response.headers['access-control-allow-headers']).toEqual('Authorization, X-Header');
+ expect(response.headers['access-control-allow-credentials']).toEqual('true');
+ expect(response.headers['access-control-max-age']).toEqual('36000');
+ });
+
+ test('GET "/api/v2" returns 200', async () => {
+ const testBed = await testBedSetup.useTestBed();
+
+ const response = await pipe(
+ testBed.req('GET'),
+ testBed.withPath('/api/v2'),
+ testBed.withHeaders({ 'Origin': 'fake-origin' }),
+ testBed.send,
+ );
+
+ expect(response.statusCode).toEqual(200);
+ expect(response.headers['access-control-allow-origin']).toEqual('fake-origin');
+ });
+ });
});

MODIFY
packages/core/src/+internal/testing/http.helper.ts
packages/core/src/+internal/testing/http.helper.ts
@@ -102,6 +102,9 @@ export const createTestRoute = (opts?: { throwError?: boolean; delay?: number; m
return { req, path, effect, item };
};
+/**
+ * @deprecated
+ */
export const createHttpServerTestBed = (server: Promise<ServerIO<HttpServer>>) => {
let httpServer: HttpServer;

refactor(integration): http - integration tests with @marblejs/testing