Detox with Mirage.js [Mock API Call] React-Native - react-native

I'm working on some tests using Detox for my React-Native application, one of those test is a flow where I need to check that the user's session is secured. If not, I'm sending an SMS Verification Code.
Test : Success to mock the POST API Call api/sessions/:sessionId, {code : 123456}
Problem : Mirage is not catching the call, so of course my Saga return an error for the fake code 123456, where I want instead Mirage.JS to return true to continue the flow.
Here are the file (file.spec.js):
import { Server } from "miragejs"
import { makeServer } from "./server";
let server;
beforeEach(() => {
server = makeServer({ environment: "development" });
})
afterEach(() => {
server.shutdown()
})
describe('SecureFlow', () => {
it("should do nav to a project and start Investment Flow", async () => {
server.get("https://random-api.eu/sessions/:sessionId", () => {
return new Response( 200, {}, { ok: true });
});
await basicNavigation(); //randomNavigation until the secure part (Screen)
await element(by.id('Accept-andLend')).tap();
await element(by.id('textInput-SMSCode')).typeText("123456");
})
})
server.js
import { Server, Model, Factory } from "miragejs";
export function makeServer({ environment = "development" } = {}) {
let server = new Server({
environment,
models: {
},
routes() {
this.post("https://random-api.eu/sessions/:sessionId", schema => {
return [{ok: true}];
});
}
});
return server;
}

Related

Test Express.js routes which rely on a TypeORM connection

I have a very basic Express.js app which I use Jest and Supertest to test. The routes are not set up until the database is connected:
class App {
public app: express.Application;
public mainRoutes: Util = new Util();
constructor() {
this.app = express();
AppDataSource.initialize()
.then(() => {
// add routes which rely on the database
this.mainRoutes.routes(this.app);
})
.catch((error) => console.log(error));
}
}
export default new App().app;
Here is my test:
describe("Util", function () {
test("should return pong object", async () => {
const res = await request(app).get("/ping");
expect(res.statusCode).toEqual(200);
expect(res.body).toEqual({ message: "pong" });
});
});
Since I put in the promise, this has been 404ing. I can't add async to the constructor. I tried refactoring the class to separate the connection with setting up the routes, but it didn't seem to help.
This works:
test("should return pong object", async () => {
setTimeout(async () => {
const res = await request(app).get("/ping");
expect(res.statusCode).toEqual(200);
expect(res.body).toEqual({ message: "pong" });
}, 1000);
});
But obviously I don't want to add a setTimeout. How is this usually done? I am new to testing.
Just remove the setTimeout() and await the call to the application. You should be initializing the application in the beforeAll() method, which I assume you have, to get the application up and running in the testing space. You should also mock your database connection, so you can fake the data you want back, and not have to wait for the external database to actually be available.
// Create a mock for your database, and have it return whatever you need
import <your-database-class> = require('database');
jest.mock('database', () => {
...
});
describe("Util", function () {
beforeAll(async () => {
app = await <whatever you do to launch your application>
});
test('should be defined', () => {
expect(app).toBeDefined();
});
test("should return pong object", async () => {
const res = await request(app).get("/ping");
expect(res.statusCode).toEqual(200);
expect(res.body).toEqual({ message: "pong" });
});
});

Why is Jest running the typescript test files and then the compiled JS test files?

When I run Jest, I get 9 failing, 11 passing out of a total of 20, but there are only 10 tests between two different test files, here it is:
const fs = require('fs');
const assert = require('assert');
import * as jwt from 'jsonwebtoken';
import * as auth from '../services/authentication-service';
const JWT_ERROR_INVALID_SIG = 'invalid signature';
describe('MMD Integration', () => {
const SERVICE = "knox";
const SERVICE_ID = "aluna1";
const badPublicKeyFile = "badkey.pub";
describe('Service Config is accessible', () => {
it('should contain data', async (done) => {
let config: {} | null = null;
config = await auth.getServiceConfig().catch(err => {
console.log("caught getServiceConfig error:", err);
return null;
});
if (config != null) {
assert.include(Object.keys(config), SERVICE);
} else {
console.log("Test failed!");
}
});
});
describe('Public Key', () => {
describe('is valid', () => {
it('should decode successfully', async (done) => {
let config: {} | null = null;
config = await auth.getServiceConfig().catch(err => {
console.log("caught getServiceConfig error:", err);
return null;
});
let publicKey: string | null = null;
if (config) {
publicKey = await auth.getServicePublicKey(SERVICE, config).catch(err => {
console.log("caught getServicePublicKey error:", err);
return null;
});
const token = await auth.genJwt(SERVICE);
if (token == null) {
console.log("genJwt returned null: stopping test");
done();
} else if (!publicKey) {
console.log("No public key: stopping test");
done();
} else {
jwt.verify(token, publicKey, (err, decoded) => {
if (err) {
console.log("WARNING: valid public key failed!", err.message);
} else if (decoded && Object.keys(decoded).includes('vendor')) {
assert.include(Object.values(decoded), SERVICE);
} else {
console.log("Test failed!");
}
});
}
}
});
});
describe('is bad', () => {
const badPublicKey = fs.readFileSync(badPublicKeyFile);
it('should fail verify', async (done) => {
const token = await auth.genJwt(SERVICE);
if (token == null) {
console.log("genJwt returned null: stopping test");
done();
} else {
jwt.verify(token, badPublicKey, (err: any, decoded: any) => {
if (err) {
assert.equal(err.message, JWT_ERROR_INVALID_SIG);
} else {
console.log("WARNING: bad public key worked!", decoded);
}
});
}
});
});
});
describe('Verify Service', () => {
describe('with valid public key', () => {
it('should succeed', async (done) => {
try {
const token = await auth.genJwt(SERVICE);
if (token == null) {
console.log("genJwt returned null: stopping test");
done();
} else {
const result = await auth.verifyService(SERVICE, token).catch(err => {
console.log("caught verifyService error: stopping test", err);
throw new Error(err);
});
assert.equal(result, "OK");
}
} catch (err) {
assert.equal(err, "OK");
}
});
});
describe('with mismatch token', () => {
it('should fail', async (done) => {
try {
const result = await auth.verifyService(SERVICE, "xyz").catch(err => {
console.log("caught verifyService error: stopping test", err);
done();
});
} catch (err) {
assert.notEqual(err, "OK");
}
});
});
});
describe('Service as real MMD', () => {
it('should fail', async (done) => {
try {
const token = await auth.genJwt("mmd");
if (token == null) {
console.log("genJwt returned null: stopping test");
throw new Error('null token');
} else {
const result = await auth.verifyService("mmd", token).catch(err => {
console.log("caught verifyService error:", err);
throw new Error(err);
});
}
} catch (err) {
assert.notEqual(err, "OK");
console.log(err);
}
});
});
});
describe('Get Token from Request Header', () => {
const someToken = "fake-jwt";
const headers = {
'Content-Type': 'application/json'
, 'Authorization': 'Bearer ' + someToken
, 'Aluna-Service': 'foobar'
};
const badHeaders2 = {
'Content-Type': 'application/json'
, 'Authorization': someToken
, 'Aluna-Service': 'foobar'
};
describe('Request header has authorization', () => {
it('should return token', () => {
const result = auth.getTokenFromAuth(headers.Authorization);
assert.equal(result, someToken);
});
});
describe('Request header is missing authorization', () => {
it('should return null', () => {
const result = auth.getTokenFromAuth('');
assert.equal(result, null);
});
});
describe('Authorization is missing Bearer', () => {
it('should return null', () => {
const result = auth.getTokenFromAuth(badHeaders2.Authorization);
assert.equal(result, null);
});
});
});
import request from 'supertest';
import { app } from '../app';
it('renders a greeting to screen', () => {
return request(app).get('/').send({ greeting: 'howdy' }).expect(200);
})
This is what I see in the terminal:
Test Suites: 3 failed, 1 passed, 4 totaload:flatten Completed in 1ms
Tests: 9 failed, 11 passed, 20 total
Snapshots: 0 total
Time: 31.358 s
Ran all test suites.
Watch Usage
› Press f to run only failed tests.
› Press o to only run tests related to changed files.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press q to quit watch mode.
› Press Enter to trigger a test run.
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
at Object.getCodec (node_modules/iconv-lite/lib/index.js:65:27)
at Object.getDecoder (node_modules/iconv-lite/lib/index.js:127:23)
at getDecoder (node_modules/raw-body/index.js:45:18)
at readStream (node_modules/raw-body/index.js:180:15)
at getRawBody (node_modules/raw-body/index.js:108:12)
[2022-03-07T18:40:25.852Z] 1.0.1-dev error: uncaughtException: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Error: Caught error after test environment was torn down
This API was meant to work with Jest or that was the original testing suite installed, but someone else came behind and started using Mocha that they are using globally on their machine. Would anyone mind also sharing why tests would pass on their global install of Mocha but not on Jest?
Just wanted to post a solution which is not buried in comments.
By default jest will find any test files in your entire project. If you are building or copying files to a build/release directory, you need to do one of the following:
(Recommended) Exclude the test files from your build pipeline. I usually create a separate tsconfig for building which excludes the test files. Your build command should point to this tsconfig: tsc --project tsconfig.build.json. Note: you can extend tsconfigs so that you don't have to manage duplicates. Here's an example of what your tsconfig.build.json might look like:
{
"extends": "./tsconfig.json",
"exclude": ["src/**/*.test.ts"]
}
-- OR --
Exclude your build directories from jest, adding testPathIgnorePatterns: ['dist/'] to your jest.config.js (assuming your compiled JavaScript files are in the dist folder)

How to send a request from Nuxt.js client over Nuxt.js server and receive the response back to the client

I'm developing a Vue.js application which has only frontend (no server) and send a lot of requests to different APIs. The originally quite simple app became more complex. And there are problems with some APIs, because browsers do not accept the responses due to CORS. That is why I'm trying to test, if I can migrate the app to Nuxt.js.
My approach is as follows (inspired by this comment), but I expect, that there is probably a better way to send the requests from the client over the server.
pages/test-page.vue
methods: {
async sendRequest(testData) {
const response = await axios.post('api', testData)
// Here can I use the response on the page.
}
}
nuxt.config.js
serverMiddleware: [
{ path: '/api', handler: '~/server-middleware/postRequestHandler.js' }
],
server-middleware/postRequestHandler.js
import axios from 'axios'
const configs = require('../store/config.js')
module.exports = function(req, res, next) {
let body = ''
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
if (req.hasOwnProperty('originalUrl') && req.originalUrl === '/api') {
const parsedBody = JSON.parse(body)
// Send the request from the server.
const response = await axios.post(
configs.state().testUrl,
body
)
req.body = response
}
next()
})
}
middleware/test.js (see: API: The Context)
export default function(context) {
// Universal keys
const { store } = context
// Server-side
if (process.server) {
const { req } = context
store.body = req.body
}
}
pages/api.vue
<template>
{{ body }}
</template>
<script>
export default {
middleware: 'test',
computed: {
body() {
return this.$store.body
}
}
}
</script>
When the user makes an action on the page "test", which will initiate the method "sendRequest()", then the request "axios.post('api', testData)" will result in a response, which contains the HTML code of the page "api". I can then extract the JSON "body" from the HTML.
I find the final step as suboptimal, but I have no idea, how can I send just the JSON and not the whole page. But I suppose, that there must be a much better way to get the data to the client.
There are two possible solutions:
Proxy (see: https://nuxtjs.org/faq/http-proxy)
API (see: https://medium.com/#johnryancottam/running-nuxt-in-parallel-with-express-ffbd1feef83c)
Ad 1. Proxy
The configuration of the proxy can look like this:
nuxt.config.js
module.exports = {
...
modules: [
'#nuxtjs/axios',
'#nuxtjs/proxy'
],
proxy: {
'/proxy/packagist-search/': {
target: 'https://packagist.org',
pathRewrite: {
'^/proxy/packagist-search/': '/search.json?q='
},
changeOrigin: true
}
},
...
}
The request over proxy can look like this:
axios
.get('/proxy/packagist-search/' + this.search.phpLibrary.searchPhrase)
.then((response) => {
console.log(
'Could get the values packagist.org',
response.data
)
}
})
.catch((e) => {
console.log(
'Could not get the values from packagist.org',
e
)
})
Ad 2. API
Select Express as the project’s server-side framework, when creating the new Nuxt.js app.
server/index.js
...
app.post('/api/confluence', confluence.send)
app.use(nuxt.render)
...
server/confluence.js (simplified)
const axios = require('axios')
const config = require('../nuxt.config.js')
exports.send = function(req, res) {
let body = ''
let page = {}
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
const parsedBody = JSON.parse(body)
try {
page = await axios.get(
config.api.confluence.url.api + ...,
config.api.confluence.auth
)
} catch (e) {
console.log('ERROR: ', e)
}
}
res.json({
page
})
}
The request over API can look like this:
this.$axios
.post('api/confluence', postData)
.then((response) => {
console.log('Wiki response: ', response.data)
})
.catch((e) => {
console.log('Could not update the wiki page. ', e)
})
Now with nuxtjs3 :
nuxtjs3 rc release
you have fetch or useFetch no need to import axios or other libs, what is great, automatic parsing of body, automatic detection of head
fetching data
you have middleware and server api on same application, you can add headers on queries, hide for example token etc
server layer
a quick example here in vue file i call server api :
const { status } = await $fetch.raw( '/api/newsletter', { method: "POST", body: this.form.email } )
.then( (response) => ({
status: response.status,
}) )
.catch( (error) => ({
status: error?.response?.status || 500,
}) );
it will call a method on my server, to init the server on root directory i created a folder name server then api, and a file name newsletter.ts (i use typescript)
then in this file :
export default defineEventHandler(async (event) => {
const {REST_API, MAILINGLIST_UNID, MAILINGLIST_TOKEN} = useRuntimeConfig();
const subscriber = await readBody(event);
console.log("url used for rest call" + REST_API);
console.log("token" + MAILINGLIST_TOKEN);
console.log("mailing list unid" + MAILINGLIST_UNID);
let recipientWebDTO = {
email: subscriber,
subscriptions: [{
"mailingListUnid": MAILINGLIST_UNID
}]
};
const {status} = await $fetch.raw(REST_API, {
method: "POST",
body: recipientWebDTO,
headers: {
Authorization: MAILINGLIST_TOKEN,
},
}).then((response) => ({
status: response.status,
}))
.catch((error) => ({
status: error?.response?.status || 500,
}));
event.res.statusCode = status;
return "";
})
What are the benefits ?
REST_API,MAILING_LIST_UNID, MAILING_LIST_TOKEN are not exposed on
client and even file newsletter.ts is not available on debug browser.
You can add log only on server side You event not expose api url to avoid some attacks
You don't have to create a new backend just to hide some criticals token or datas
then it is up to you to choose middleware route or server api. You don't have to import new libs, h3 is embedded via nitro with nuxtjs3 and fetch with vuejs3
for proxy you have also sendProxy offered by h3 : sendProxy H3
When you build in dev server and client build in same time(and nothing to implement or configure in config file), and with build to o, just don deploy your project in static way (but i think you can deploy front in static and server in node i don't know)

How to test Axios reject condition using Jest

I wrote a unit test for some Axios calls in my component. I verified the success path, where the call resolves successfully, but I am not able to verify the failure path, where the call rejects. How do I use mocks to verify this?
Here's a snippet of my FetchImage.vue component:
methods: {
preparedFetch() {
axios.get(this.imageurl).then(result => {
this.imageInformation.title = result.data.title;
this.imageInformation.copyright = result.data.copyright;
this.imageInformation.detailExplanation = result.data.explanation;
this.imageInformation.date = result.data.date;
this.imageInformation.urlinfo = result.data.url;
this.resultArrived = true;
this.$emit('imagefetched',this.imageInformation);
})
.catch( error => {
this.errorMessage = "Information not found";
this.resultArrived = true;
});
}
}
And my test for when the call rejects (for an invalid URL):
describe('Invalid response',async () => {
beforeEach(() => {
axios.get.mockClear();
axios.get.mockReturnValue(Promise.reject({}));
});
it('Invalid URL verfication', async () => {
// Given
const result = {
errorMessage : "Information not found",
resultArrived : true,
fetchStatus : true
};
// Fetch the error result
axios.get.mockReturnValue(Promise.resolve(result));
const fetchwrapper = mount(FetchImage);
fetchwrapper.vm.imageurl = "https://invalid.request.gov";
fetchwrapper.vm.preparedFetch();
await fetchwrapper.vm.$nextTick();
// Validate the result
expect(axios.get).not.toHaveBeenCalledWith('https://api.nasa.gov/planetary/apod?api_key=vME6LAMD7IhEiy7rDmjfIaG6MhiKbu1MNIqxtqd1');
expect(axios.get).toHaveBeenCalledWith("https://invalid.request.gov");
expect(axios.get).toHaveBeenCalledTimes(1);
expect(fetchwrapper.vm.errorMessage.length).not.toEqual(0);
expect(fetchwrapper.vm.errorMessage).toBe("Information not found");
});
});
Your catch block isn't running because the mock return value is using Promise.resolve() when it actually should be Promise.reject():
describe('Invalid response',async () => {
it('Invalid URL verfication', async () => {
// axios.get.mockReturnValue(Promise.resolve(result)); // DON'T DO THIS
axios.get.mockReturnValue(Promise.reject(result));
});
});
You have to reject the value by using the built-in jest method.
describe('Invalid response', async () => {
it('Invalid URL verfication', async () => {
axios.get.mockRejectedValue(result);
});
});

NuxtJs - Cannot read property 'headers' of undefined

I'm a newbie in NuxtJs. I'm trying to implement an external API Call with axios which I get token and store it on cookie. Everything works well in development. But when I try to run npm run generate it gives me errors that I don't know what to do.
When I delete nuxtSeverInit, npm run generate runs smoothly. And after some research, i think that nuxtServerInit that I'm using shouldn't be used. Can anyone please tell me how to make it work.
This is the first project in a new company, so I'm trying to prove myself. Please help me with it. Will you.
Click here for image that shows the error that appears after npm run generate
This is store/index.js file
import Vuex from 'vuex'
var cookieparser = require('cookieparser')
const createStore = () => {
return new Vuex.Store({
state: {
auth: null,
},
mutations: {
update (state, data) {
state.auth = data
}
},
actions: {
nuxtServerInit ({ commit }, { req }) {
let accessToken = null
if (req.headers.cookie) {
var parsed = cookieparser.parse(req.headers.cookie)
if(parsed){
accessToken = parsed.auth
}
}
commit('update', accessToken)
},
}
})
}
export default createStore
middleware/authenticated.js file
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.auth) {
return redirect('/login')
}
}
middleware/notAuthenticated.js file
export default function ({ store, redirect }) {
// If the user is authenticated redirect to home page
if (store.state.auth) {
return redirect('/app/dashboard')
}
}
login.vue file
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
this.button_title = 'One moment ...';
let submitted_user_data = {
'username': this.emailAddress,
'client_id': this.user_uuid,
'password': this.password,
}
MerchantServices.do_user_login(submitted_user_data)
.then(response => {
let access_token = response.data.access_token;
this.postLogin(access_token);
})
.catch(error => {
this.$refs.invalid_credentials.open();
this.button_title = 'Sign in'
});
return;
}
});
},
postLogin: function(access_token_val) {
if(access_token_val != ''){
setTimeout(() => {
const auth = {
accessToken: access_token_val
}
this.$store.commit('update', auth)
Cookie.set('auth', auth)
this.$refs.invalid_credentials.open();
this.button_title = 'Sign in'
this.$router.push('/app/dashboard')
}, 1000)
}else{
alert('hello')
}
},
and the last user login api call which also returns the token.
do_user_login(user){
var user_details = 'username='+user.username+'&client_id='+ user.client_id +'&grant_type=password&password='+user.password+''
return axios.post('myapiurl', user_details )
.then(response => {
return response;
});
},
Acording to Nuxt Docs req is not available on nuxt generate.
You should use nuxt build and than nuxt start after that.