I have some problems with my tests since I upgraded from electron v11 to v20.
With this update the remote was moved from electron to a new module #electron/remote.
In jest.config.js I have the electron module mock mapping: electron: '<rootDir>/tests/mock/electron.mock.ts'
...
const mockIpcRenderer = {
on: jest.fn(),
send: jest.fn()
};
const mockRemote = {
app: {
getPath: mockGetPath,
getAppPath() {
return '/app/working/path';
}
},
process: {
env: jest.fn()
}
};
...
export const ipcRenderer = mockIpcRenderer;
export const remote = mockRemote;
So, after the update I extracted the remote part from the mock to a new file: remote.mock.ts
const mockApp = {
getPath: mockGetPath,
getAppPath() {
return '/app/working/path';
}
};
const mockProcess = {
env: jest.fn()
};
function mockGetPath(path: string) {
return 'somtething';
}
export const app = mockApp;
export const process = mockProcess;
and I added this line to my test file:
jest.mock('#electron/remote', () => require('../mock/remote.mock'));
The problem is when I run the test I get TypeError: Cannot read property 'on' of undefined.
The ipcRenderer is undefined and I don't know why ?
When I added that jest.mock('#electron/remote', () => require('../mock/remote.mock')); to the test file, the mocks from electron.mock.ts are not defined anymore, for some reason.
In the jest.config.js I added start and end symbols and added a new line for #electron/remote and it works.
moduleNameMapper: {
'^#electron/remote$': '<rootDir>/tests/mock/electron.mock.ts',
'^electron$': '<rootDir>/tests/mock/electron.mock.ts',
},
Related
I have a method, in a class, that only executes its action when NODE_ENV === 'test'.
Here is the test that I set the env to anything to test the failing scenario:
it('returns Left on clearDatabase when not in test environment', async () => {
const { sut } = await makeSut()
process.env.NODE_ENV = 'any_environment'
const result = await sut.clearDatabase()
process.env.NODE_ENV = 'test'
expect(result.isLeft()).toBe(true)
})
Here is the method:
async clearDatabase (): Promise<Either<Error, void>> {
if (process.env.NODE_ENV !== 'test') {
return left(new Error('Clear database is allowed only in test environment'))
}
try {
const { database } = this.props.dataSource
await this.mongoClient.db(database).dropDatabase()
return right()
} catch (error) {
return left(error)
}
}
The problem is that when the method do it's verification, the value in NODE_ENV wasn't changed at all, it has its initial value (test). If I log the value, after setting it, in test file it's there, only the object can't see this change. In jest it works just fine. How can I set/mock it properly in vitest?
Here you find a StackBlitz with an example scenario: https://stackblitz.com/edit/node-lr72gz?file=test/example.unit.test.ts&view=editor
using nestjs framework and with a repository class that uses mongoose to do the CRUD operations we have a simple users.repository.ts file like this:
#Injectable()
export class UserRepository {
constructor(#InjectModel(User.name) private userModel: Model<UserDocument>) {}
async create(createUserInput: CreateUserInput) {
const createdUser = new this.userModel(createUserInput);
return await createdUser.save();
}
}
async findById(_id: MongooseSchema.Types.ObjectId) {
return await this.userModel.findById(_id).exec();
}
and it works normally when the server is up.
consider this users.repository.spec file :
import { Test, TestingModule } from '#nestjs/testing';
import { getModelToken } from '#nestjs/mongoose';
import { Model } from 'mongoose';
// User is my class and UserDocument is my typescript type
// ie. export type UserDocument = User & Document; <-- Mongoose Type
import { User, UserDocument } from '../domain/user.model';
import { UserRepository } from './users.repository';
//import graphqlScalars from 'graphql-scalar-types';
describe('UsersRepository', () => {
let mockUserModel: Model<UserDocument>;
let mockRepository: UserRepository;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getModelToken(User.name),
useValue: Model, // <-- Use the Model Class from Mongoose
},
UserRepository,
//graphqlScalars,
],
}).compile();
// Make sure to use the correct Document Type for the 'module.get' func
mockUserModel = module.get<Model<UserDocument>>(getModelToken(User.name));
mockRepository = module.get<UserRepository>(UserRepository);
});
it('should be defined', () => {
expect(mockRepository).toBeDefined();
});
it('should return a user doc', async () => {
// arrange
const user = new User();
const userId = user._id;
const spy = jest
.spyOn(mockUserModel, 'findById') // <- spy
.mockResolvedValue(user as UserDocument); // <- set resolved value
// act
await mockRepository.findById(userId);
// assert
expect(spy).toBeCalled();
});
});
so my question:
for the should return a user doc test i get TypeError: metatype is not a constructor when and i guess
.mockResolvedValue(user as UserDocument);
should be fixed.
Note:graphql is used the query to the API and i have no idea that if the scalars should be provieded or not, if i uncomment the scalar, the expect(mockRepository).toBeDefined(); test would not pass any more
so any idea to fix the test would be apreciated.
to handle a chained .exec we should define it via mockReturnThis():
static findById = jest.fn().mockReturnThis();
I needed the constructor to be called via new so i preferd to define a mock class in this way:
class UserModelMock {
constructor(private data) {}
new = jest.fn().mockResolvedValue(this.data);
save = jest.fn().mockResolvedValue(this.data);
static find = jest.fn().mockResolvedValue(mockUser());
static create = jest.fn().mockResolvedValue(mockUser());
static remove = jest.fn().mockResolvedValueOnce(true);
static exists = jest.fn().mockResolvedValue(false);
static findOne = jest.fn().mockResolvedValue(mockUser());
static findByIdAndUpdate = jest.fn().mockResolvedValue(mockUser());
static findByIdAndDelete = jest.fn().mockReturnThis();
static exec = jest.fn();
static deleteOne = jest.fn().mockResolvedValue(true);
static findById = jest.fn().mockReturnThis();
}
I've set up CodeceptJS for a project and use it to test various end-to-end scenarios.
Now I want to extend the tests-suite to also run unit-tests to verify functionality of custom JS functions.
For example: I have a global object App that has a version attribute. As a first test, I want to confirm that App.version is present and has a value.
My first attempt is a test.js file with the following code:
Feature('Unit Tests');
Scenario('Test App presence', ({ I }) => {
I.amOnPage('/');
I.executeScript(function() {return App.version})
.then(function(value) { I.say(value) } );
});
Problems with this code
The major issue: How can I assert that the App.version is present?
My script can display the value but does not fail if it's missing
My code is very complex for such a simple test.
I'm sure there's a cleaner/faster way to perform that test, right?
Here is a solution that works for me:
Read data from the browser:
I created a custom helper via npx codecept gh and named it BrowserAccess.
The helper function getBrowserData uses this.helpers['Puppeteer'].page.evaluate() to run and return custom code from the browser scope. Documentation for .evaluate()
Custom assertions:
Install the codeceptjs-assert package, e.g. npm i codeceptjs-assert
Add the AssertWrapper-helper to the codecept-config file. This enables checks like I.assert(a, b)
Full Code
codecept.conf.js
exports.config = {
helpers: {
AssertWrapper: {
require: "codeceptjs-assert"
},
BrowserAccess: {
require: './browseraccess_helper.js'
},
...
},
...
}
browseraccess_helper.js
const Helper = require('#codeceptjs/helper');
class BrowserAccess extends Helper {
async getBrowserData(symbolName) {
const currentPage = this.helpers['Puppeteer'].page;
let res;
try {
res = await currentPage.evaluate((evalVar) => {
let res;
try {
res = eval(evalVar);
} catch (e) {
}
return Promise.resolve(res);
}, symbolName);
} catch (err) {
res = null;
}
return res;
}
}
jsapp_test.js (the test is now async)
Feature('Unit Tests');
Scenario('Test App presence', async ({ I }) => {
I.amOnPage('/');
const version = await I.getBrowserData('App.version');
I.assertOk(version);
});
I'm writing unit tests with jest and I want to mock the socket.io-client module so I can test an es6 class that's using it.
Here's a simplified version of the class:
import io from "socket.io-client"
export default class BotClient {
io: SocketIOClientStatic
client: SocketIOClient.Socket | undefined
connected: boolean = false
attemptReconnection: boolean = true
constructor() {
this.io = io
}
init() {
try {
this.reconnectToApi()
} catch (error) {
console.error(error)
}
}
reconnectToApi(): void {
const interval = setInterval(() => {
if (this.client?.connected || !this.attemptReconnection) {
this.connected = true
this.addHooks()
clearInterval(interval)
return
}
this.client = this.io.connect("myurl")
}, 5000)
}
And I wrote my test to mock the socket.io-client module and return a custom implementation object that returns a mock function for the connect method like this:
import BotClient from "../src/lib/BotClient"
import io from "socket.io-client"
const mockClient = {
connect: jest.fn(() => {
connected: true
}),
}
jest.mock("socket.io-client", () => {
return mockClient
})
test("client will attempt connection until told to stop", () => {
jest.useFakeTimers()
const reconnectSpy = jest.spyOn(client, "reconnectToApi")
const hookSpy = jest.spyOn(client, "addHooks")
client.init("my-bot-id")
const callCount = 3
jest.advanceTimersByTime(5000 * callCount)
expect(reconnectSpy).toHaveBeenCalledTimes(1)
expect(hookSpy).toHaveBeenCalledTimes(1)
})
But when I try to run the test I get the error:
TypeError: Cannot read property 'connect' of undefined
I'm looking over the jest docs on mocking modules and using custom implementations and it seems like I'm doing it right, but obviously I'm not :/ I feel like I'm tripping over information in the docs.
How would I do this correctly? What part am I misunderstanding?
I am using vue/cli 3 configured for cypress e2e tests. The e2e test scenario works fine but I also wish to use cypress for unit tests. I installed cypress-vue-unit-test but when loading a single component (using mountVue) cypress fails to interpret the Vue syntax ( etc).
I presume I have to add configuration so that the correct web pack loaders are used at the preprocessor stage when cypress bundles the files. I have been unable to figure out how to accomplish this as there is no web pack config file in my project and I am not sure how to modify the preconfigured set-up. Can anyone guide me?
Thanks phoet, you pointed me in the right direction. The solution was to place the configuration in tests/e2e/plugins/index.js with the following content (probably could be refined):
const webpack = require("#cypress/webpack-preprocessor");
const VueLoader = require("vue-loader/lib/plugin");
const webpack_vue_cypress_config = {
webpackOptions: {
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader"
},
{
test: /\.css$/,
use: ["vue-style-loader", "css-loader"]
}
]
},
resolve: {
extensions: [".js", ".vue", ".json"],
alias: {
vue$: "vue/dist/vue.esm.js",
"#": "../../"
}
},
plugins: [new VueLoader()]
},
watchOptions: {}
};
module.exports = (on, config) => {
on("file:preprocessor", webpack(webpack_vue_cypress_config));
return Object.assign({}, config, {
fixturesFolder: "tests/e2e/fixtures",
integrationFolder: "tests/e2e/specs",
screenshotsFolder: "tests/e2e/screenshots",
videosFolder: "tests/e2e/videos",
supportFile: "tests/e2e/support/index.js"
});
};
Thanks Linus; that's much cleaner
const webpack = require("#cypress/webpack-preprocessor");
const options = {
webpackOptions: require("#vue/cli-service/webpack.config.js"),
watchOptions: {}
};
module.exports = (on, config) => {
on("file:preprocessor", webpack(options));
return Object.assign({}, config, {
fixturesFolder: "tests/e2e/fixtures",
integrationFolder: "tests/e2e/specs",
screenshotsFolder: "tests/e2e/screenshots",
videosFolder: "tests/e2e/videos",
supportFile: "tests/e2e/support/index.js"
});
};