I have a class like the one shown here:
import {otherStuff} from 'somewhere'
const promiseFtp = require('promise-ftp');
export class processor(){
ftp: any;
public async connect(){
ftp = new promiseFtp();
await ftp.connect(myConnectionObject);
this.ftp = ftp;
}
}
My problem is that I have no way to mock promiseFtp. I cant find any documentation in Jasmine that supports this sort of testing. I would let it just call the connect function, but I dont want it to actually connect to any ftp site.
I might be able to use something like proxyquire, but my boss is against it since we moved to typescript, and that was supposed to get rid of proxyquire. I cannot use proxyquire Typescript should have a way around this somehow.
I even tried to my my own module that exports promiseFtp, then mocks that module, but I just cant do it. How do you mock a class so that it wont get called.
Related
I'm currently developing a TypeScript library to be used internally in my company's projects, which are a mix of JavaScript and TypeScript Node applications.
So, I want to be able to import the library's default class properly in JavaScript with CommonJS. However, I can only use my class in these programs if I import it with require('...').default.
I managed to make it work previously by adding a module.exports = MyClass at the end of the file, but the autocomplete of VSCode doesn't seem to catch up to that, still suggesting the default before showing the methods.
How can I make it so I can import my class by just using require('...') while still making sure the autocomplete is properly working?
Thanks.
EDIT:
For clarity, what I want to do is to be able to do this:
const MyClass = require('MyTypeScriptClass');
instead of:
const MyClass = require('MyTypeScriptClass').default;
Edit
I decided to not use export default when exporting from a library, since it seems to be what every other library does.
The TypeScript equivalent should be this:
class MyClass {
}
export = MyClass
Can also be inlined to export = class MyClass { ... if that is preferred.
(To import an export like this in a TypeScript project you need to set the esModuleInterop flag.)
I used loopback 4 to bootstrap my API application and developed some parts of it.
Now I'm trying to access repositories outside the controllers and tried everything I could but didn't help. None of my searches did too. For example I have a controller in which I can access repo such way.
constructor(
#repository(UserRepository) public userRepository: UserRepository){}
But if it isn't a controller it won't work and I found out I had a wrong understanding about #repository and #inject decorators.
My Case:
I want to run a cron-job to perform an update operation on database every day at a specific time.
The thing is I think I should create a service or something like that to expose database operations so it can be accessible anywhere.
The issue you're trying to tackle comes down to dependency injection. It's described at https://loopback.io/doc/en/lb4/Dependency-injection.html but that article does not explain how to create a separate class that works with dependency injection.
In short, in order to perform the dependency injection through decorators, LoopBack needs to be the one to construct the class you're writing, so that it can pick what you need from its Context. So, instead of writing new ClassSomething, it has to be #inject(ClassSomething) or #service(ClassSomething), and those decorators only work in a class also instantiated by LoopBack. In other words, your class has to be in the "call tree" from the original application class (application.ts) for dependency injection to work.
The easiest way to do what you want to do is to use LoopBack's Cron component: see https://loopback.io/doc/en/lb4/Running-cron-jobs.html. You could convert your class to a CronJob if it has no other purpose, or let the CronJob create an instance of your class. In the latter case, you need to bind your class to the application Context so that the CronJob can access it through decorators. You can do so in two ways:
Convert your class to a Service (see https://loopback.io/doc/en/lb4/Service.html and https://loopback.io/doc/en/lb4/Service-generator.html). If what your class does can be thought of as a service, this is the way to go.
Inject your class with: #service(MyServiceClass) myService: MyServiceClass
Directly bind your class to the application context, in application.ts:
this.bind('MyClass').toClass(MyClass);
Inject your class with: #inject(MyClass) myClass: MyClass
The latter option is not best practice AFAIU as it does not adhere to IoC-principles (https://en.wikipedia.org/wiki/Inversion_of_control) - basically, by hard-coding the class binding in the application class, it is not exactly modular. When your class is converted to a service (and placed in src/services), it will automatically be added to the Context, meaning you can inject it everywhere using #service().
I also experience the same(cron word). I couldn't find any solution still in the documentation. But still, I have achieved this in this way.
just call repository as like class with dependency in it. do this in your index.ts file where you start the application.
async function startCronJobs(app: SawLoopbackApplication) {
const userRepo = app.repository(UserRepository);
const userRepoInstance = await userRepo.getValue(app);
const cron = new CronComponent(userInstance);
//---- Exicute your cron here
new CronJob('0 6 * * *', function () {
cron.sendMorningMail()
}).start();
//---- Exicute your Second Cron
new CronJob('0 8 * * 1', function () {
cron.weeklyAppraisalsToAgent()
}).start();
}
call your cron in the functional component and execute a raw query to get the details from DB etc...
import {MysqlDataSource} from '../../datasources'
const query = `select * from accounts where IP="${ipAddress}" ORDER By
createdAt DESC Limit 1`
const dataSource = new MysqlDataSource()
const accountDetails = await dataSource.execute(query)
console.log(accountDetails)
instead of a raw query. call your repository from the function component below
import {MysqlDataSource} from '../datasources'
import {ContactUsUserDetailsRepository} from '../repositories'
const contactUsUserDetailsRepository = new ContactUsUserDetailsRepository(new MysqlDataSource)
function saveContactDetails(){
const payload={UID:"Unique ID",name:"Durai"}
await contactUsUserDetailsRepository.create(payload)
}
I would prefer 2 because, if you have a lot of imports in your cron class constractor you need to pass all of them while calling a function like in the 3rd point.
Have fun:)
Is it possible to add params dynamically in one annotation on NestJS?
Like this:
const myParam = config.service.param;
#Annotation(myParam)
public myMethod() {
}
Technically, the answer is yes, but only if the value passed to the decorator is already resolved at the time of importing the file containing the decorator. In other words, you can't use class members (like Nest's ConfigService) because decorators run against the file as soon as the file is imported, but you could use something like process.env if you use dotenv and read the config first thing, before you import any other files
I am having strange issues with Typescript when I import things from a file which exports them. Sometimes I will export a function, then import it to another file, then I use the function and it is not a function anymore. When I define the function in the same file, all of a sudden the function is a function?!?!?
Why would a function stop being a function when it is exported? I have had similar problems with classes too.
The hard part of this issue is I can't recreate a simple example because it only happens when I am using some kind of higher level package.
For example, I had a similar issue with sequelize-typescript here: my github issue with typescript-sequelize
Below is some codes showing off the basic issue I'm having with one of the decorators from InversifyJS.
container.ts
import {fluentProvide} from "inversify-binding-decorators";
export const provideSingleton = (identifier: any) => {
return fluentProvide(identifier)
.inSingletonScope()
.done(true);
};
test.service.ts
import {provideSingleton} from './container'
#provideSingleton(TYPES.TEST)
export default class TestService {}
The strangest thing is when I put the provideSingleton in the same file as the TestService, everything works!?!?!
Basically to recreate the issue, simply follow the example from here: inversify-binding-decorators - Using #provideFluent multiple times. However there is an issue with the example, so please see this issue: fluentProvide example needed. The above provideSingleton reflects the changes from that issue. Then you simply import the provideSingleton function from another file instead of defining it in the same like in the example.
Can anyone explain to me what I'm missing? Why oh why would certain exported items not bee seen as the type they are? Is there a step I'm not seeing that NodeJS takes to make the item actually exported and therefore different? Can I force the function to resolve as a function so it can be used as such?
ENV:
NodeJS: 10.9.0
Typescript: 3.0.1
Mac: 10.13.16
So it looks like you can get issues like this when NodeJS can't handle a recursive import. I'm not exactly sure how to check you are getting this error other than your symptoms are like what I stated above. Basically the recursion caused my function to not load and therefore undefined is not a function.
It would be easy to notice if you had code like so:
a.ts
import B from './b';
export default class A extends B {}
b.ts
import A from './a';
export default class B extends A {}
In my case, I think my function provideSingleton did not like the file I put it in because of some conflicting code in the file, which all I had was:
import {Container} from 'inversify';
import "reflect-metadata";
import {fluentProvide} from "inversify-binding-decorators";
const container = new Container();
function ProvideSingleton(identifier: any) {
return fluentProvide(identifier)
.inSingletonScope()
.done(true);
}
export {container, ProvideSingleton}
In the end, if this issue comes up, try another file for your function and pay good attention to how the order of the loading happens. Although NodeJS handles recursive imports most of the time, you can still trip it out.
I'm writing a module that adds some functionalities to testing suites our team is using. It's a project that has main typescript file, that wraps other exports from our project.
index.d.ts:
export * from "./OneModule"
export * from "./AnotherModule"
One of the modules has a functionality of adding new methods to Chai.js assertion library. This is achieved by using it's functions for adding new methods, for example:
assertion.addChainableMethod('newMethod', callback, chainCallback)
will add ability to write statements like expect(something).to.be.newMethod(). TypeScript though, doesn't recognize those new methods, since the type Assertion in #types/chai obviously doesn't have those functions.
I've created interface definition to be merged with the Chai Assertion like this:
declare namespace Chai {
interface Assertion {
newMethod(something: string): Assertion;
newMethod: Assertion;
}
}
The addChainableMethod adds new method so it can be also chaines as expect(something).to.be.newMethod.equals() that's why it has to be defined both as property and method.
The problem is, no matter how I add the above declaration to index.d.ts the new assertions aren't visible in the importing project. I suspect it's somehow wrapped in module namespace(but I might be very wrong about that). I know I can do #types/newAssertions and define them there, but that requires users to include 2 projects in package.json, and also requires our developers to handle 2 projects at once. How can I export both my Chai namespace extension and all the modules I have?
The best I've managed to do is something like that:
declare module 'my-module' {
function AddNewAssertions(chai: any, utils: any): void;
}
declare namespace Chai {
interface Assertion { //same as above }
}
But with this I cannot expose my other modules, and it works only when I'm only exposing AddNewAssertions function, which adds those assertions. This works perfectly though.
I knocked up a very basic test app for this, and I had some success with adding:
declare global {
namespace Chai {
interface Assertion {
newMethod(something: string): Assertion;
newMethod: Assertion;
}
}
}
This makes the extension visible in your other modules, not just the one with the declaration.