nestjsx-automapper profiles not found in nestjs test modules - testing

Im trying to make unit testing for some features in my NestJS API.
I make use of nestjsx-automapper. The exception happens only while testing any service that uses mappings. I use #nestjs/testing and jest.
I have managed to replicate the error in this test:
describe('UserController', () => {
let userServiceInterface: UserServiceInterface;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
imports: [AutomapperModule.withMapper('user')],
providers: [
{
provide: 'UserServiceInterface',
useClass: UserService,
},
],
}).compile();
userServiceInterface = app.get<UserServiceInterface>(
'UserServiceInterface',
);
});
describe('find', () => {
it(`should return user with name 'pipo'`, () => {
const user = userServiceInterface.find('pipo');
expect(user.username).toBe('pipo');
});
});
});
I've been looking for different ways to configure profiles, but importing them is the only way I've found.
Thanks in advance.

I figured out how nestjsx-automapper load profiles.
As you can't provide profiles in modules, you depend on that the application runs the profiles first, and it is handled by AutomapperModule from nestjsx-automapper.
I don't know why nest Modules load AutomapperModule and consequently the profiles, but Test Modules doesn't, I will have to work more with nest to understand it.
To tell Test Modules to load AutomapperModule (as they don't by default) on init, use:
moduleRef.get(AutomapperModule).onModuleInit();

Related

I'm switching from Vuex to Pinia in Vue 3 and my unit tests are now failing. I can't seem to be able to create custom mock actions

Here's a simple example on the forgot password reset page of my app, I would want to bypass the server side and just have the password reset to succeed on click so I would write a test and use the custom test store like so:
const customStore = {
state() {
return {
Authentication: {
passwordResetSuccess: false,
},
};
},
mutations: {
SET_RESET_PASSWORD_SUCCESS(state) {
state.Authentication.passwordResetSuccess = true;
},
},
actions: {
forgotPasswordResetPassword() {
this.commit('SET_RESET_PASSWORD_SUCCESS');
},
},
};
Then I could include the custom store in my beforeEach() and it worked great. I've tried everything I can think of to get this to work with pinia, but it doesn't seem to work.
I'm using jest along with vue/test-utils.
I basically tried just creating the test pinia store, but I can't figure out how to get the component to use the custom test store.
const useCustomStore = defineStore('AuthenticationStore', {
state: () => ({
passwordResetSuccess: false,
}),
actions: {
forgotPasswordResetPassword() {
this.passwordResetSuccess = true;
},
},
});
const authenticationStore = useCustomStore();
I can't directly add it as a plugin because it can't find an active instance of pinia.
I went through this guide: https://pinia.vuejs.org/cookbook/testing.html#unit-testing-a-store
and I also tried using jest mock as described here: https://stackoverflow.com/a/71407557/4697639
But it still failed.
If anyone has any idea how to create a custom store that can be used by the component and actually hits the custom actions, I could really use some help figuring this out. Thank you!!
Tao mentioned in the comments that this isn't a good way to do unit tests. I will mark this as resolved and fix how I do the testing.

cypress cy.request 401 unauthorized [duplicate]

I want to save/persist/preserve a cookie or localStorage token that is set by a cy.request(), so that I don't have to use a custom command to login on every test. This should work for tokens like jwt (json web tokens) that are stored in the client's localStorage.
To update this thread, there is already a better solution available for preserving cookies (by #bkucera); but now there is a workaround available now to save and restore local storage between the tests (in case needed). I recently faced this issue; and found this solution working.
This solution is by using helper commands and consuming them inside the tests,
Inside - cypress/support/<some_command>.js
let LOCAL_STORAGE_MEMORY = {};
Cypress.Commands.add("saveLocalStorage", () => {
Object.keys(localStorage).forEach(key => {
LOCAL_STORAGE_MEMORY[key] = localStorage[key];
});
});
Cypress.Commands.add("restoreLocalStorage", () => {
Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
});
});
Then in test,
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
Reference: https://github.com/cypress-io/cypress/issues/461#issuecomment-392070888
From the Cypress docs
For persisting cookies: By default, Cypress automatically clears all cookies before each test to prevent state from building up.
You can configure specific cookies to be preserved across tests using the Cypress.Cookies api:
// now any cookie with the name 'session_id' will
// not be cleared before each test runs
Cypress.Cookies.defaults({
preserve: "session_id"
})
NOTE: Before Cypress v5.0 the configuration key is "whitelist", not "preserve".
For persisting localStorage: It's not built in ATM, but you can achieve it manually right now because the method thats clear local storage is publicly exposed as Cypress.LocalStorage.clear.
You can backup this method and override it based on the keys sent in.
const clear = Cypress.LocalStorage.clear
Cypress.LocalStorage.clear = function (keys, ls, rs) {
// do something with the keys here
if (keys) {
return clear.apply(this, arguments)
}
}
You can add your own login command to Cypress, and use the cypress-localstorage-commands package to persist localStorage between tests.
In support/commands:
import "cypress-localstorage-commands";
Cypress.Commands.add('loginAs', (UserEmail, UserPwd) => {
cy.request({
method: 'POST',
url: "/loginWithToken",
body: {
user: {
email: UserEmail,
password: UserPwd,
}
}
})
.its('body')
.then((body) => {
cy.setLocalStorage("accessToken", body.accessToken);
cy.setLocalStorage("refreshToken", body.refreshToken);
});
});
Inside your tests:
describe("when user FOO is logged in", ()=> {
before(() => {
cy.loginAs("foo#foo.com", "fooPassword");
cy.saveLocalStorage();
});
beforeEach(() => {
cy.visit("/your-private-page");
cy.restoreLocalStorage();
});
it('should exist accessToken in localStorage', () => {
cy.getLocalStorage("accessToken").should("exist");
});
it('should exist refreshToken in localStorage', () => {
cy.getLocalStorage("refreshToken").should("exist");
});
});
Here is the solution that worked for me:
Cypress.LocalStorage.clear = function (keys, ls, rs) {
return;
before(() => {
LocalStorage.clear();
Login();
})
Control of cookie clearing is supported by Cypress: https://docs.cypress.io/api/cypress-api/cookies.html
I'm not sure about local storage, but for cookies, I ended up doing the following to store all cookies between tests once.
beforeEach(function () {
cy.getCookies().then(cookies => {
const namesOfCookies = cookies.map(c => c.name)
Cypress.Cookies.preserveOnce(...namesOfCookies)
})
})
According to the documentation, Cypress.Cookies.defaults will maintain the changes for every test run after that. In my opinion, this is not ideal as this increases test suite coupling.
I added a more robust response in this Cypress issue: https://github.com/cypress-io/cypress/issues/959#issuecomment-828077512
I know this is an old question but wanted to share my solution either way in case someone needs it.
For keeping a google token cookie, there is a library called
cypress-social-login. It seems to have other OAuth providers as a milestone.
It's recommended by the cypress team and can be found on the cypress plugin page.
https://github.com/lirantal/cypress-social-logins
This Cypress library makes it possible to perform third-party logins
(think oauth) for services such as GitHub, Google or Facebook.
It does so by delegating the login process to a puppeteer flow that
performs the login and returns the cookies for the application under
test so they can be set by the calling Cypress flow for the duration
of the test.
I can see suggestions to use whitelist. But it does not seem to work during cypress run.
Tried below methods in before() and beforeEach() respectively:
Cypress.Cookies.defaults({
whitelist: "token"
})
and
Cypress.Cookies.preserveOnce('token');
But none seemed to work. But either method working fine while cypress open i.e. GUI mode. Any ideas where I am coming short?
2023 Updated on Cypress v12 or more:
Since Cypress Version 12 you can use the new cy.session()
it cache and restore cookies, localStorage, and sessionStorage (i.e. session data) in order to recreate a consistent browser context between tests.
Here's how to use it
// Caching session when logging in via page visit
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('form').contains('Log In').click()
cy.url().should('contain', '/login-successful')
})

CypressIO tests to be executed in multiple URL

An Ecommerce website have different URL's for different countries. And each of these website have to be tested. We have set of automation scripts written in CYPRESSIO. Looking for ideas how these scripts can be rerun for different URL's.
Example URL's for different countries
UK: https://www.abc.co.uk
CH: https://www.abc.ch
DE: https://www.abc.de
There are some functionalities which are country specific and hence we have to run tests for all the URL's . Any ideas and leads would be appreciated.
Thanks in advance :)
Set the tests inside a data loop
const urls = ['https://www.abc.co.uk', 'https://www.abc.ch', 'https://www.abc.de'];
urls.forEach(url => {
describe(`Testing url: ${url}`, () => {
before(Cypress.config('baseUrl', url))
it('...', () => {
})
})
Testing a simplified scenario,
support/index.js
beforeEach(() => {
console.log('beforeEach in support', Cypress.config('baseUrl'))
})
dynamic-baseUrl.spec.js
const urls = ['https://www.abc.co.uk', 'https://www.abc.ch', 'https://www.abc.de'];
urls.forEach(url => {
describe(`Testing url: ${url}`, () => {
before(() => Cypress.config('baseUrl', url))
it('sees the required baseURL', () => {
console.log('it', Cypress.config('baseUrl'))
})
})
})
console output
beforeEach in support https://www.abc.co.uk
it https://www.abc.co.uk
beforeEach in support https://www.abc.ch
it https://www.abc.ch
beforeEach in support https://www.abc.de
it https://www.abc.de
Cypress log
Testing url: https://www.abc.co.uk
...passed
Testing url: https://www.abc.ch
...passed
Testing url: https://www.abc.de
...passed
One way would be to create country-specific cypress.json files.
For eg. cypress-de.json
Inside it you can define baseURL and country-specific test cases using testFiles[] as:
{
"testFiles": [
"TC_01.spec.js",
"TC_02.spec.js",
"TC_03.spec.js",
"TC_04.spec.js"
],
"baseUrl": "https://www.abc.de"
}
Now when you want to run tests, all you have to do is pass the relevant cypress.json file through CLI using the command:
npx cypress run --config-file cypress-de.json

How can I properly access store actions in a Vue test?

I have a test that is passing, but I don't understand why I have to write this way. Here is the test setup:
describe("Photo Due", () => {
const localVue = createLocalVue();
localVue.use(Vuex);
let store;
let actions = {
"api_data/photoAction": jest.fn(),
"api_data/selectPhoto": jest.fn(),
"modals/openCloseModal": jest.fn()
},
beforeEach(() => {
store = new Vuex.Store({
state: {
token: "abc",
GLN: "123",
GLT: "456",
isDesktop: true
},
getters: {
"auth/getToken": state => state.token,
"auth/getGLN": state => state.GLN,
"auth/getGLT": state => state.GLT,
"app/getIsDesktop": state => state.isDesktop
},
actions
});
});
...
Here is my test that calls a function, which calls an action:
it("reportPhoto", async () => {
const wrapper = factory();
wrapper.vm.reportPhoto();
wrapper.vm.$nextTick();
expect(actions["api_data/selectPhoto"]).toHaveBeenCalled(); // THIS IS THE LINE IN QUESTION
});
When the test is setup this way everything passes. But it doesn't seem right that I am defining actions outside of the store and accessing it that way in the test by calling actions["api_data/selectSnap"]. I copied this approach from a guide on from the vue-test-utils website. If I don't need to access actions and getters from the store then why not bypass the store altogether and jut define random functions that mock my vuex functionality?
I think I don't understand what is happening under the hood fully, but shouldn't I be accessing the action through the store? This is what I am having trouble doing.
My questions are 1) Do I need to access the actions through the store or am I overthinking what should be a basic test? 2) If I do need to access the action through the store, how do I go about doing that?
It really depends on what kind of test you are trying to do and if you want to test a fully functional store or just check that it gets called.
I recommend this post. There are differents approach explained along with several examples. It helped me when I needed to write some store oriented unit tests

How to cache .mp4 files in Safari with workbox-webpack-plugin?

I'm having exactly the same issue reported at https://github.com/GoogleChrome/workbox/issues/1663 which describes an issue that occurs exclusively in Safari where mp4 videos are not rendered after being cached by the service worker.
I'm using workbox-webpack-plugin, so the instructions provided in the comment https://github.com/GoogleChrome/workbox/issues/1663#issuecomment-448755945 will not work in my case. I'm not being able to require workbox-range-requests plugin in my webpack config file and pass it to the runtime caching options because I believe this package is intended for browser usage only. My workbox config is precaching .mp4 assets and uses a network first strategy for runtime caching.
How can I setup workbox-range-requests with workbox-webpack-plugin?
EDIT: Following Jeff's answer below, I've adjusted my webpack config to the following:
new WorkboxPlugin.InjectManifest({
swSrc: serviceWorkerSrcPath,
swDest: serviceWorkerBuildPath,
importsDirectory: 'sw',
})
The build produces the following service worker:
importScripts("/_build/sw/precache-manifest.8a0be820b796b153c97ba206d9753bdb.js", "https://storage.googleapis.com/workbox-cdn/releases/3.6.2/workbox-sw.js");
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
workbox.routing.registerRoute(
/.*\.mp4/,
new workbox.strategies.CacheFirst({
cacheName: 'videos',
plugins: [
new workbox.cacheableResponse.Plugin({ statuses: [200] }),
new workbox.rangeRequests.Plugin(),
],
}),
);
If forgot to mention previously, but I've also added crossOrigin="anonymous" attribute to the video elements.
EDIT:
Repro that demonstrates it does not work as expected on Safari: https://github.com/acostalima/workbox-range-requests-mp4-demo
There's specific guidance for this use case in the "Serve cached audio and video" recipe in the Workbox documentation.
You can continue using the workbox-webpack-plugin, but I'd suggest using it in InjectManifest mode, which will give you control over the top-level service worker file. That will in turn make it possible to follow the recipe.
This documentation has guidance on configuring workbox-webpack-plugin in InjectManifest mode.
I had the same issue with Safari and managed to resolve it by removing my video from the precahe list self.__precacheManifest and instead by adding it in the service worker's install handler:
self.addEventListener('install', (event) => {
const urls = [/* videoUrl */];
const cacheName = 'videos';
event.waitUntil(caches.open(cacheName).then((cache) => cache.addAll(urls)));
});
Looking at the logs, it seemed that otherwise only the precache was used to respond to the request for the video resource and not the router.
Although the docs say that adding mp4s to the precache cache and then configuring the range plugin to handle precache mp4s is supposed to work, in practice, it wasn't. Removing mp4s from the precache and configuring your own video cache with the range plugin did the trick for me. Don't forget to add the crossorigin="anonymous" tag to your videos!
Here's how I did it (webpack 5, workbox 6):
// src/service-worker.js
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { cacheNames } from 'workbox-core';
import { precacheAndRoute } from 'workbox-precaching';
import { RangeRequestsPlugin } from 'workbox-range-requests';
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
const allEntries = self.__WB_MANIFEST; // Injected by WorkboxWebpackPlugin at compile time
const videoEntries = allEntries.filter((entry) => entry.url.endsWith('.mp4'));
const restEntries = allEntries.filter((entry) => !entry.url.endsWith('.mp4'));
precacheAndRoute(restEntries);
const videoCacheName = `${cacheNames.prefix}-videos-${cacheNames.suffix}`;
self.addEventListener('install', (event) => {
const allVideosAddedToCache = caches.open(videoCacheName).then((videoCache) => {
const videoUrls = videoEntries.map((entry) => entry.url);
return videoCache.addAll(videoUrls);
});
event.waitUntil(allVideosAddedToCache);
});
registerRoute(
(route) => route.url.pathname.endsWith('.mp4'),
new CacheFirst({
cacheName: videoCacheName,
plugins: [new CacheableResponsePlugin({ statuses: [200] }), new RangeRequestsPlugin()],
})
);
// webpack.config.js
plugins: [
new WorkboxWebpackPlugin.InjectManifest({
swSrc: 'src/service-worker.js',
}),
]
// index.tsx
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});
}