I have an API class that I am trying to use in a React app.
// API file
class API {
...
}
export default API;
// Other file
import API from "utils/API";
const api = new API();
And I am getting the error:
TypeError: _API.default is not a constructor
But.. it seems like my default is set?
My Jest setup is like this:
"jest": {
"setupFiles": [
"./jestSetupFile.js"
],
"testEnvironment": "jsdom",
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)|expo(nent)?|#expo(nent)?/.*|#expo-google-fonts/.*|react-navigation|#react-navigation/.*|#unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-router-native/.*|#invertase/react-native-apple-authentication/.*)"
]
},
My strong guess is that this is due to a configuration of my babel, webpack or package.json.
What could be causing this?
Note, I want to be clear, this doesn't happen whatsoever in my main application, only in Jest testing
If I change it to a named export/import, I get this:
TypeError: _API.API is not a constructor
Extremely confusing behavior.
As mentioned by others, it would be helpful to see a minimum reproducible example.
However, there is one other possible cause. Are you mocking the API class in your test file at all? This problem can sometimes happen if a class is mistakenly mocked as an "object" as opposed to a function. An object cannot be instantiated with a "new" operator.
For example, say we have a class file utils/API like so:
class API {
someMethod() {
// Does stuff
}
}
export default API;
The following is an "incorrect" way to mock this class and will throw a TypeError... is not a constructor error if the class is instantiated after the mock has been created.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns an object
return {
someMethod: () => {}
};
})
// This will throw the error
const api = new API();
The following will mock the class as a function and will accept the new operator and will not throw the error.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns a function
return jest.fn().mockImplementation(() => ({
someMethod: () => {}
}));
})
// This will not throw an error anymore
const api = new API();
Trying adding "esModuleInterop": true, in your tsconfig.json. BY default esModuleInterop is set to false or is not set. B setting esModuleInterop to true changes the behavior of the compiler and fixes some ES6 syntax errors.
Refer the documentation here.
This was ultimately due to additional code inside the file that I was exporting the class from.
import { store } from "root/App";
if (typeof store !== "undefined") {
let storeState = store.getState();
let profile = storeState.profile;
}
At the top, outside my class for some functionality I had been working on.
This caused the class default export to fail, but only in Jest, not in my actual application.
You'll need to export it like this :
export default class API
You could try with:
utils/API.js
export default class API {
...
}
test.js
import API from "utils/API";
const api = new API();
I'm adding this because the issue I had presented the same but has a slightly different setup.
I'm not exporting the class with default, i.e.
MyClass.ts
// with default
export default MyClass {
public myMethod()
{
return 'result';
}
}
// without default, which i'm doing in some instances.
export MyClass {
public myMethod()
{
return 'result';
}
}
When you don't have the default, the import syntax changes.
In a (jest) test if you follow the convention where you do have export default MyClass(){};
then the following works.
const MOCKED_METHOD_RESULT = 'test-result'
jest.mock("MyClass.ts", () => {
// will work and let you check for constructor calls:
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
});
However, if you don't have the default and or are trying to mock other classes etc. then you need to do the following.
Note, that the {get MyClass(){}} is the critical part, i believe you can swap out the jest.fn().mockImplementation() in favour of jest.fn(()=>{})
jest.mock("MyClass.ts", () => ({
get MyClass() {
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
},
}));
So the issue is the way in which you access the contents of the class your mocking. And the get part allows you to properly define class exports.
I resolved this error by using below code.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: // REPLICATE YOUR API CONSTRUCTOR BEHAVIOUR HERE BY ADDING CLASS
})
If you want to mock complete API class, please check the below snippet.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: class {
constructor(args) {
this.var1 = args.var1
}
someMethod: jest.fn(() => Promise.resolve())
},
}));
I am trying to test a function that is declared inside another function(Parent). I tried a few things but was not able to test that. I am using react library and JEST testing framework in the project.
Here is the service file (service.ts) function which I am trying to test:
import { useThisHook } from './base-hooks';
export function someFunction(param: string) {
const [
resolveFunction,
,
{ loading, error },
] = useThisHook();
const childFun = (param : number) => {
resolveFunction(someArguments);
}
return{
someValue: childFun
}
}
Here is the spec file code:
import * as SomeHooks from './base-hooks';
import * as Service from './service';
describe('Service function', () => {
beforeEach(() => {
jest.spyOn(SomeHooks, 'useThisHook').mockReturnValue(
{
//Some Value
}
);
});
test('Some function test', () => {
const someFunctionResponse = Service.someFunction('string');
expect(someFunctionResponse).toEqual({
someValue: expect.any(Function),
});
});
});
Till here it is fine. Tests are also getting passed but the problem is that childFn is not getting the coverage and that's what my requirement is. I am new to React and Jest. I am not able to understand how can I achieve that. I tried many things but didn't succeed.
Your childFn is not being called, that's why it's not getting coverage.
You could either refactor the whole childFn to a new hook and test it individually with something like react-hooks-testing-library.
Or you could separate the childFn and declarate it outside the someFunction scope pass the resolveFunction and then test it.
You only get coverage if the code is actually called, not when it is declared.
Is it possible to spyOn helper class? In the below code, StatefulPatternService.init() is calling a WebSocketHelper.
I would like to spyOn WebSocketHelper and mock the subscribeFn
export class WebSocketHelper{
private url: string;
constructor(url){
this.url = url;
init();
}
init(){
// init websocket & other login
}
}
#Injectable({
providedIn: 'root'
})
export class StatefulPatternService {
constructor(){}
private callback(_data){ }
init(){
let wsHelper = new WebSocketHelper('/subscribe/topic'); // <-- How to spyOn???
wsHelper.subscribeFn = this.callback;
// ...
}
}
If spyOn won't be possible, then how it can be re-written so that this test can be covered?
Your challenge will be getting a hold of 'wsHelper' in order to spy on it. One thought: can you refactor to make wsHelper a class-scope variable instead? Then you could spyOn it when you get the service in the test suite, for example, something like:
service = TestBed.get(StatefulPatternService);
let spy = spyOn(service.wsHelper, 'subscribeFn');
Update
From the comments to my answer it looks like what you are really trying to do is verify that the constructor was called with the proper url. Since you are saving that in a class variable, there should be no need to spy on the constructor, but rather just test the value of the saved variable. As I mentioned in the comments, to do this you will need two things: to make wsHelper a class level variable, and to add a method on the WebSocketHelper class that returns the value of the private variable 'url' so you can test it. I've set up a stackblitz to demonstrate what I'm talking about here: STACKBLITZ Here is a snippet from that stackblitz:
describe('MyService', () => {
let myService: StatefulPatternService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [/* any imports needed */],
providers: [ StatefulPatternService ]
});
myService = TestBed.get(StatefulPatternService);
});
it('should be createable', () => {
expect(myService).toBeTruthy();
});
it('should construct wsHelper properly', () => {
myService.init();
expect(myService.wsHelper.get_url()).toEqual('/subscribe/topic');
})
});
I've been using Subject from rxjs library to make an event emitter in my angular applications but I'm not able to use it in Angular 6 as
This module isn't present there
Cannot find module 'rxjs/Subject'
Is being thrown.
Is there a new way for what I've been doing like this.
import { Subject } from 'rxjs/Subject';
private Login = new Subject<boolean>();
Then doing this this.Login.next(true)
I was able to change state in my project of the user:
loginService.Login.subscribe(state => {
console.log('Login State')
console.log(state)
})
How to do the above stuff in Angular 6
In Angular 6, you can do it like this:
import { Subject } from 'rxjs'; // or import { Subject } from 'rxjs/index';
// ....
private Login = new Subject<boolean>();
// ...
loginService.Login.subscribe(state => {
// ... your code
})
I have a class constructor, with a function I want to stub:
class Service {
constructor(){}
async someFunction() {
try {
// does stuff
}
catch (e) {}
}
}
In the file I want to test, this is imported an used like so:
const { Service } = require('something')
const newService = new Service('xyz')
I'm struggling to get this to import & stub correctly in my tests.
Currently am importing like this:
t.context.service = {
Service: class Service {
constructor () {
this.someFunction = sinon.stub()
}
}
}
This import seems to work, but then I can't get a reference back to it through the constructed version. Any help on this one?
I want to be able to make an assertion like:
t.true(t.context.service.Service.someFunction.calledOnce)
AVA doesn't provide any stubbing. Have a look at https://github.com/testdouble/testdouble.js/ or http://sinonjs.org/.