tsyringe - Injecting a dependency with overloaded constuctor - javascript

Hi friends how are you doing?
I'm trying to do something different, I don't know if it's away from the concept itself but it would help me to achieve what I'm trying to do in an elegant way.
I'm using a repository pattern and in the implementation I want to use a overloaded constructor and use an optional argument, basically passing some adicional information when it's needed.
The problem is, it's working great when the constructor is empty, but by the time change de signature to receive one more argument, the TSYSRINGE throws an execption.
I Really think that I'm missing something really simple, but I can't figure what. Could you please help me on this one? Thanks
ERROR:
Error: Cannot inject the dependency at position #0 of "ListProjectsServices" constructor. Reason:
TypeInfo not known for "ProjectsRepository"
Controller
export default class ProjectsController {
public async index(request: Request, response: Response): Promise<void> {
const listProjectsServices = container.resolve(ListProjectsServices);
const projects = await listProjectsServices.execute();
response.json(projects);
}
Service
#injectable()
export default class ListProjectsServices {
constructor(
#inject('ProjectsRepository')
private ProjectsRepository: IProjectsRepository,
) {}
public async execute(): Promise<Projects[]> {
const ProjectsList = await this.ProjectsRepository.findAllProjects();
return ProjectsList;
}
}
Container - to create the injection token
container.registerSingleton<IProjectsRepository>(
'ProjectsRepository',
ProjectsRepository,
);
Repository - Notice the extra_details argument in the constructor
after adding it, the problem occurs
#EntityRepository(Projects)
export default class ProjectsRepository implements IProjectsRepository {
private ormRepository: Repository<Projects>;
constructor(extra_details?: object) {
this.ormRepository = getRepository(Projects);
}
[...]

Today I went through this same problem.
Read this article on circular dependencies.
It tells us to use the delay function imported from tsyringe.
In the article, he tells us to use the delay inside the constructor.
But you, like me, are not directly sending the object in the inject, but a registered key.
Then you must use the delay in your container file, around the repository object try this:
import { container, delay } from 'tsyringe';
container.registerSingleton<IProjectsRepository>(
'ProjectsRepository',
delay(() => ProjectsRepository),
);
Insert the delay in all your injections that depend on an asynchronous repository
It can also be an error in the seo ormconfig.json, in the entities property.
Make sure the path is pointing to your entities:
"entities": [
"./src/modules/**/infra/typeorm/entities/*.ts"
],

We had the same problem here and we didn't want to have to use the delay function in the controller because we would be breaking the dependency injection principle... One way to solve the problem is to import your "Container" file into the main project file, in our case called "server.ts" and installing a lib called "reflect-metadata".
server.ts:
import * as dotenv from 'dotenv';
dotenv.config();
import express from 'express';
import 'express-async-errors';
import 'reflect-metadata'; // here is reflect-metadata import!
import config from './config/application';
import './infra/container'; // here is container import!
import { errorHandler } from './infra/http/middlewares/errorHandler';
import useSwagger from './infra/http/middlewares/swagger';
import { routes } from './infra/http/routes';
import { morganMiddleware } from './infra/logging/morgan';
export const app = express();
app.use(morganMiddleware);
app.use(express.json());
app.use(`${config.prefix}/api`, routes);
app.use(`${config.prefix}`, express.static('public'));
useSwagger(`${config.prefix}/api/docs`, app);
app.all(`${config.prefix}`, (_, res) => res.redirect(`${config.prefix}/api/docs`));
app.use(errorHandler);
container/index.ts:
import { container } from 'tsyringe';
import { RecurrenceNotificationUseCase } from '../../application/useCases/RecurrenceNotificationUseCase';
import { PubSubImplementation } from '../pubsub/PubSubImplementation';
container.registerSingleton('IPubSub', PubSubImplementation);
container.registerSingleton('IRecurrenceNotificationUseCase', RecurrenceNotificationUseCase);

Related

Spying On/Mocking Import of an Import

I'm writing unit tests using vitest on a VueJS application.
As part of our application, we have a collection of API wrapper services, e.g. users.js which wraps our relevant API calls to retrieve user information:
import client from './client'
const getUsers = () => {
return client.get(...)
}
export default {
getUsers
}
Each of these services utilise a common client.js which in turn uses axios to do the REST calls & interceptor management.
For our units tests, I want to check that the relevant url is called, so want to spy on, or mock, client.
I have followed various examples and posts, but struggling to work out how I mock an import (client) of an import (users.js).
The closest I've been able to get (based on these posts - 1, 2) is:
import { expect, vi } from 'vitest'
import * as client from '<path/to/client.js>'
import UsersAPI from '<path/to/users.js>'
describe('Users API', () => {
beforeEach(() => {
const spy = vi.spyOn(client, 'default') // mock a named export
expect(spy).toHaveBeenCalled() // client is called at the top of users.js
})
test('Users API.getUsers', () => {
UsersAPI.getUsers()
expect(spy).toHaveBeenCalled()
})
})
but it's tripping on:
❯ async frontend/src/api/client.js:3:31
2| import store from '#/store'
3|
4| const client = axios.create({
| ^
5| headers: {
6| 'Content-Type': 'application/json'
where it's still trying to load the real client.js file.
I can't seem to mock client explicitly because the import statements run first, and so client is imported inside users.js before I can modify/intercept it. My attempt at the mocking was as follows (placed between the imports and the describe):
vi.mock('client', () => {
return {
default: {
get: vi.fn()
}
}
})
Mocking a module
vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:
// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js') // => resolves to path/to/client.js
It often helps to use path aliases here.
Spying/mocking a function
To spy on or mock a function of the mocked module, do the following in test():
Dynamically import the module, which gets the mocked module.
Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '#/users.js'
vi.mock('#/client')
describe('Users API', () => {
test('Users API.getUsers', async () => {
1️⃣
const client = await import('#/client')
2️⃣
const response = { data: [{ id: 1, name: 'john doe' }] }
client.default.get = vi.fn().mockResolvedValue(response)
const users = await UsersAPI.getUsers()
expect(client.default.get).toHaveBeenCalled()
expect(users).toEqual(response)
})
})
demo
Late to the party but just in case anyone else is facing the same issue.
I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.
import { client } from 'client';
vi.mock('client', () => {
const client = vi.fn();
client.get = vi.fn();
return { client }
});
Then in those tests calling client.get() behind the scenes as a dependency, just add
client.get.mockResolvedValue({fakeResponse: []});
and the mocked function will be called instead of the real implementation.
If you are using a default export, look at the vitest docs since you need to provide a default key.
If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.
I've accepted the above answer, as that did address my initial question, but also wanted to include this additional step I required.
In my use case, I need to mock an entire module import, as I had a cascading set of imports on API files that in turn, imported more and more dependencies themselves.
To cut this, I found this in the vuex documentation about mocking actions:
https://vuex.vuejs.org/guide/testing.html#testing-actions
which details the use of webpack and inject-loader to substitute an entire module with a mock, preventing the source file loading at all.

Cypress - import and export functions

How can I better organize my cypress code for testing so that I only need to import some functions?
I want to start by creating a file in which to authenticate on the page, and then I want to import it in a test with several functions.
I tried the following export code and it seems to have been incorrect, with errors:
export function login() {
cy.visit('https://*********')
cy.get('input[name="Parameter.UserName"]').type('*****')
cy.get('input[name="Parameter.Password"]').type('*****')
cy.contains('Login').click()
}
export default {login};
And in test :
import {login} from 'elements/pages/login.js'
Your import needs a relative URL
import {login} from '../elements/pages/login.js' // relative from cypress/integration
or if under cypress/suport/elements
import {login} from '../support/elements/pages/login.js'
Absolute imports (where path has no leading ./ or ../) are presumed to be library packages in node_modules.
Cypress provides something called the Custom commands for this purpose, you can read about it here.
Go to cypress/support/commands.js and write all your code here like:
Cypress.Commands.add('login', () => {
cy.visit('https://*********')
cy.get('input[name="Parameter.UserName"]').type('*****')
cy.get('input[name="Parameter.Password"]').type('*****')
cy.contains('Login').click()
})
And then in your any of your test, you can directly add:
cy.login()
all cys must be excuted in testing content, maybe u can try like this:
export default {
// props
visit: (url)=> { return cy.visit(url) }
get: (el)=> { return cy.get(el) }
// methods
login(){
cy.visit('https://*********')
cy.get('input[name="Parameter.UserName"]').type('*****')
cy.get('input[name="Parameter.Password"]').type('*****')
cy.contains('Login').click()
}
}

How to access fastify app instance inside an imported file

Im working on a fastify powered rest api and im trying to separate my code into logical files. I have some class objects that i import into my server where fastify is defined and the fastify.listen happens. What i cant figure out is how to access the fastify instance inside a file that i import.
app.js
import fastify from 'fastify'
import autoload from 'fastify-autoload'
import { join } from 'desm'
export default function (opts) {
const app = fastify(opts)
app.register(autoload, {
dir: join(import.meta.url, 'routes')
})
return app
}
server.js
import createApp from './app.js'
import 'dotenv/config.js'
import Sessions from './scripts/sessions.js'
import emitter from 'central-event'
async function start () {
const app = createApp({ logger: true })
await app.listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0')
const intervalSeconds = process.env.intervalSeconds * 1000
setInterval(function () {
emitter.emit('heartbeat')
}, intervalSeconds)
}
start()
I want to access the fastify app instance inside sessions.js that is imported into server.js I have tried various things like importing fastify and creating the app in there hoping that it would be inherited etc. Any help would be appreciated.
I found the info i needed. Inside the imports for sessions and others im exporting the class via export default (app) => class Sessions{}
then inside server i import it:
import SessionsImp from './scripts/sessions.js'
then pass the app via:
const SessionsClass = SessionsImp(app)
const Sessions = new SessionsClass()
It seems convoluted but it works as i wish, then i can decorate app with the sessions and other classes im loading and use them inside routes and plugins as i wish.

Difference between app.get and router.get (Express)

I personally prefer the first approach as I feel it keeps my logic inside the controller and my routes are super obvious when all they do is use a method from a controller and map it to a route.
But I am a very junior developer and I keep finding that people use the second approach more. Would anyone with more knowledge than I can explain to me if my approach is bad and why. I understand both, but as I said I am barely beginning my developer career and I need to make my code readable/simple so I need to learn what approach is better for what case.
Thanks!
//this is /routes/product.ts
import product from "../controllers/Product";
module.exports = function(app: Application) {
app.get("/api/product", product.getAll);
app.post("/api/product", product.create);
app.get("/api/product/:_id", product.getOne);
app.put("/api/product/:_id/edit", product.update);
app.delete("/api/product/:_id", product.delete);
};
---------------------------------------------------
//this is app.ts
import express, { Application } from "express";
class App {
app: Application;
constructor() {
this.app = express();
this.routes();
}
routes() {
require("../routes/product")(this.app);
}
}
}
export default App;
//this is .routes/index.ts
import { Router, Request, Response } from "express";
import Product from "../models/Product";
const router = Router();
router
.route("/create")
.get((req: Request, res: Response) => {
res.render("product/create");
})
.post(async (req: Request, res: Response) => {
const { title, description } = req.body;
const newProduct = new Product({ title, description });
await newProduct.save();
res.redirect("/product/list");
});
export default router;
----------------------------------------------------
//this is app.ts
import express, { Application } from "express";
import indexRoute from "./routes/index";
class App {
app: Application;
constructor() {
this.app = express();
this.routes();
}
routes() {
this.app.use(indexRoute);
}
}
export default App;
There is absolutely no difference, in fact, app is actually is a Router internally (or at least it uses one internally).
The main advantages of using a Router would be:
Router level middleware, your approach would mean any middleware added would be applied for all routes.
Router level error handling, you can catch an error and stop it from bubbling up to the global error handler (if necessary).
Relative URLs, your approach means you need to specify the full sub-path each time i.e. /api/products/:_id/edit Vs /:_id/edit
Routers plugin really nicely to Express (they work just like any other middleware)
Easier to unit test if necessary

Import components for server-side rendering in ES6

I've got a nice little ES6 React component file (simplified for this explanation). It uses a library that is browser-specific, store This all works beautifully on the browser:
/app/components/HelloWorld.js:
import React, { Component } from 'react';
import store from 'store';
export default class HelloWorld extends Component {
componentDidMount() {
store.set('my-local-data', 'foo-bar-baz');
}
render() {
return (
<div className="hello-world">Hello World</div>
);
}
}
Now I'm trying to get it to render on the server as follows, using babel-register:
/server/routes/hello-world.js:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import HelloWorld from '../../app/components/HelloWorld'
export default function(req, res) {
res.render('root', {
reactHTML: ReactDOMServer.renderToString(<HelloWorld />),
});
}
I get an error from the node server saying "window is not defined" due to importing 'store'. Ideally I could conditionally import by detecting the environment (node vs browser), but conditional imports aren't supported in ES6.
What's best way to get around this? I don't actually need to execute the browser code (in this case componentDidMount won't be called by ReactDOMServer.renderToString) just get it running from node.
One way would be using babel-rewire-plugin. You can add it to babel-register via the plugins option
require('babel/register')({
plugins: ['babel-rewire-plugin']
});
Then rewire your store dependency to a mocked store:
HelloWorld.__Rewire__('store', {
set: () => {} // no-op
});
You can now render HelloWorld from the server peacefully.
If you want to suppress the load of some npm module, you can just mock it.
Put this on your node.js application setup, before the HelloWorld.js import:
require.cache[require.resolve('store')] = {
exports: {
set() {} // no-op
}
};
This value will be used instead of the real module, which doesn't need on your purposes. Node.js module API is stable, so this behavior will not be broken and you can rely on it.

Categories

Resources