How to mock/replace getter function of object with Jest? - javascript

In Sinon I can do the following:
var myObj = {
prop: 'foo'
};
sinon.stub(myObj, 'prop').get(function getterFn() {
return 'bar';
});
myObj.prop; // 'bar'
But how can I do the same with Jest?
I can't just overwrite the function with something like jest.fn(), because it won't replace the getter
"can't set the value of get"

For anyone else stumbling across this answer, Jest 22.1.0 introduced the ability to spy on getter and setter methods.
Edit: like in scieslak's answer below, because you can spy on getter and setter methods, you can use Jest mocks with them, just like with any other function:
class MyClass {
get something() {
return 'foo'
}
}
jest.spyOn(MyClass.prototype, 'something', 'get').mockReturnValue('bar')
const something = new MyClass().something
expect(something).toEqual('bar')

You could use Object.defineProperty
Object.defineProperty(myObj, 'prop', {
get: jest.fn(() => 'bar'),
set: jest.fn()
});

If you care about spying only, go for #Franey 's answer. However if you actually need to stub a value for the getter, this is how you can do it:
class Awesomeness {
get isAwesome() {
return true
}
}
describe('Awesomeness', () => {
it('is not always awesome', () => {
const awesomeness = new Awesomeness
jest.spyOn(awesomeness, 'isAwesome', 'get').mockReturnValue(false)
expect(awesomeness.isAwesome).toEqual(false)
})
})

In my unity test case, as it is expected to mock external dependencies, following the thomaux's answer, i had to use a real instance instead of a mocked one in beforeEach function, see the snippet bellow.
//declaration of MyExternalConfigService with getter
#Injectable()
export class MyExternalConfigService {
constructor(private readonly configService: ConfigService) {}
get myProp():string {
return this.configService.get<string>('context.myProp')
}
}
//beforeEach unit test configuration
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MyServiceToBeTested,
{
provide: MyExternalConfigService,
useValue: new MyExternalConfigService(new ConfigService())
}
]
}).compile()
service = module.get<MyServiceToBeTested>(
MyServiceToBeTested
)
configService = module.get<MyExternalConfigService>MyExternalConfigService)
})
//mocking a value to your test case
it('my test case', ()=>{
jest
.spyOn(configService, 'myProp', 'get')
.mockImplementationOnce(() => 'mockedValue')
...
)

Related

Jest - Mock a Class from a Module (and a function that uses the get keyword) [duplicate]

In Sinon I can do the following:
var myObj = {
prop: 'foo'
};
sinon.stub(myObj, 'prop').get(function getterFn() {
return 'bar';
});
myObj.prop; // 'bar'
But how can I do the same with Jest?
I can't just overwrite the function with something like jest.fn(), because it won't replace the getter
"can't set the value of get"
For anyone else stumbling across this answer, Jest 22.1.0 introduced the ability to spy on getter and setter methods.
Edit: like in scieslak's answer below, because you can spy on getter and setter methods, you can use Jest mocks with them, just like with any other function:
class MyClass {
get something() {
return 'foo'
}
}
jest.spyOn(MyClass.prototype, 'something', 'get').mockReturnValue('bar')
const something = new MyClass().something
expect(something).toEqual('bar')
You could use Object.defineProperty
Object.defineProperty(myObj, 'prop', {
get: jest.fn(() => 'bar'),
set: jest.fn()
});
If you care about spying only, go for #Franey 's answer. However if you actually need to stub a value for the getter, this is how you can do it:
class Awesomeness {
get isAwesome() {
return true
}
}
describe('Awesomeness', () => {
it('is not always awesome', () => {
const awesomeness = new Awesomeness
jest.spyOn(awesomeness, 'isAwesome', 'get').mockReturnValue(false)
expect(awesomeness.isAwesome).toEqual(false)
})
})
In my unity test case, as it is expected to mock external dependencies, following the thomaux's answer, i had to use a real instance instead of a mocked one in beforeEach function, see the snippet bellow.
//declaration of MyExternalConfigService with getter
#Injectable()
export class MyExternalConfigService {
constructor(private readonly configService: ConfigService) {}
get myProp():string {
return this.configService.get<string>('context.myProp')
}
}
//beforeEach unit test configuration
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MyServiceToBeTested,
{
provide: MyExternalConfigService,
useValue: new MyExternalConfigService(new ConfigService())
}
]
}).compile()
service = module.get<MyServiceToBeTested>(
MyServiceToBeTested
)
configService = module.get<MyExternalConfigService>MyExternalConfigService)
})
//mocking a value to your test case
it('my test case', ()=>{
jest
.spyOn(configService, 'myProp', 'get')
.mockImplementationOnce(() => 'mockedValue')
...
)

I can't access modified window.location object jest in another module

I want to mock window.location.search.
config.test.js
import config from './config'
import { MULTIPLE_VIDEOS } from './Constants/flagKeys'
describe('', () => {
const flag = { [MULTIPLE_VIDEOS]: true }
global.window = Object.create(window)
Object.defineProperty(window, 'location', {
value: {}
})
afterAll(() => {
global.window = null
})
it('Mark query string flag as true', () => {
global.window.location.search = `?${MULTIPLE_VIDEOS}=true`
expect(config.flags).toEqual(flag)
})
})
config.js
export default { flags: getFlagsFromQueryString() }
function getFlagsFromQueryString () {
const queryString = qs.parse(window.location.search.slice(1))
const flags = {}
Object.entries(queryString).forEach(([name, value]) => {
flags[name] = value.toLowerCase() === 'true'
})
return flags
}
Though I set search value in location object before calling config.flags, I can't access it inside the function, It always returns empty string.
I want window.location.search.slice(1) to return ?multipleVideos=true inside getFlagsFromQueryString function instead of empty string, since I changed the value in the test file.
One thing I noticed, when I export the function and call in the test file it works.
For this type of complex uses. I would recommend you to create a Window service/util class and expose methods from there. easy to test and mock.
Sample:
// Window.js
class Window {
constructor(configs) {}
navigate(href) {
window.location.href = href;
}
}
export default new Window({});
Now you can easily mock Window.js. It is similar to DI pattern.

How to reset a spy in Jasmine?

I have an issue where I've set up a mock service as a spy.
mockSelectionsService = jasmine.createSpyObj(['updateSelections']);
I then call that stub method twice, each time in a different test. The problem is that when i expect() the spy with .toHaveBeenCalledWith() the toHaveBeenCalledWith method also contains the arguments it was passed from the first test which produces a false positive on my second test.
How do I wipe/clear/reset the spyObject for my next test so that it no longer believes it as been called at all?
Initialisation of services/components
beforeEach(() => {
mockSelectionsService = jasmine.createSpyObj(['updateSelections']);
TestBed.configureTestingModule({
declarations: [QuickSearchComponent, LoaderComponent, SearchComponent, SearchPipe, OrderByPipe],
providers: [OrderByPipe, SearchPipe, SlicePipe, {provide: SelectionsService, useValue: mockSelectionsService}],
imports: [FormsModule, HttpClientModule]
});
fixture = TestBed.createComponent(QuickSearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
fixture.componentInstance.templates = mockTemplates;
fixture.componentInstance.manufacturers = mockManufacturers;
});
const spy = spyOn(somethingService, "doSomething");
spy.calls.reset();
This resets the already made calls to the spy. This way you can reuse the spy between tests. The other way would be to nest the tests in another describe() and put a beforeEach() in it too.
Type 1:
var myService = jasmine.createSpyObj('MyService', ['updateSelections']);
myService.updateSelections.calls.reset();
Type 2:
var myService = spyOn(MyService, 'updateSelections');
myService.updateSelections.calls.reset();
Note: Code above is tested on Jasmine 3.5
The mock service with the default return value can be set in the beforeEach() , but if you want to change the mocked service response later, do not call fixture.detectChanges() in beforEach, you can call it in each spec after applying required changes (if any), if you want to change the mock service response in an specific spec, then add it to that spec before the fixture.detectChanges()
beforeEach(() => {
serviceSpy = jasmine.createSpyObj('RealService', ['methodName']);
serviceSpy.methodName.and.returnValue(defaultResponse);
TestBed.configureTestingModule({
providers: [{provide: RealService, useValue: serviceSpy }],
...
})
...
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
// do not call fixture.detectChanges() here
});
it('test something with default mocked service response', () => {
fixture.detectChanges();
....
});
it('test something with a new mocked service response', () => {
serviceSpy.methodName.and.returnValue(newResponse);
fixture.detectChanges();
....
});
I use it like this:
let service: anyTypeYouWant;
let spyOnMethod: jasmine.Spy<(stringInput: string) => string>;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(..Your_Inject..);
spyOnMethod= spyOn(service, "myMethod");
});
it('Your Describe', () => {
expect(service.myMethod).toHaveBeenCalled();
})
add to Jelle's answer, if you got Cannot read property 'reset' of undefined when attempting mockSelectionsService.calls.reset(), try:
const spy = spyOn(somethingService, "doSomething");
spy.mockRestore();
reference: jest spyon demo: https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname

Test constructor calling a method with Sinon

I'd like to ensure that the constructor is calling a method when instantiated with Sinon, however, I can't seem to get this to work, as I believe the sinon is not watching the correct instantiation:
class Test {
constructor() {
this.someFunction();
}
someFunction() {
return 1;
}
}
... and the test
describe('constructor', () => {
it('should call someFunction()', () => {
const spyFunc = new Spy(new Test(), 'someFunction');
expect(spyFunc.calledOnce).to.be.true;
});
});
Try to spy to Test.prototype.someFunction before invoking constructor.
Something like this
sinon.spy(Test.prototype, 'someFunction')
const spyFunc = new Test();
expect(spyFunc.someFunction.calledOnce).to.be.true;

How to stub a method of jasmine mock object?

According to the Jasmine documentation, a mock can be created like this:
jasmine.createSpyObj(someObject, ['method1', 'method2', ... ]);
How do you stub one of these methods? For example, if you want to test what happens when a method throws an exception, how would you do that?
You have to chain method1, method2 as EricG commented, but not with andCallThrough() (or and.callThrough() in version 2.0). It will delegate to real implementation.
In this case you need to chain with and.callFake() and pass the function you want to be called (can throw exception or whatever you want):
var someObject = jasmine.createSpyObj('someObject', [ 'method1', 'method2' ]);
someObject.method1.and.callFake(function() {
throw 'an-exception';
});
And then you can verify:
expect(yourFncCallingMethod1).toThrow('an-exception');
If you are using Typescript, it's helpful to cast the method as Jasmine.Spy. In the above Answer (oddly I don't have rep for comment):
(someObject.method1 as Jasmine.Spy).and.callFake(function() {
throw 'an-exception';
});
I don't know if I'm over-engineering, because I lack the knowledge...
For Typescript, I want:
Intellisense from the underlying type
The ability to mock just the methods used in a function
I've found this useful:
namespace Services {
class LogService {
info(message: string, ...optionalParams: any[]) {
if (optionalParams && optionalParams.length > 0) {
console.log(message, optionalParams);
return;
}
console.log(message);
}
}
}
class ExampleSystemUnderTest {
constructor(private log: Services.LogService) {
}
doIt() {
this.log.info('done');
}
}
// I export this in a common test file
// with other utils that all tests import
const asSpy = f => <jasmine.Spy>f;
describe('SomeTest', () => {
let log: Services.LogService;
let sut: ExampleSystemUnderTest;
// ARRANGE
beforeEach(() => {
log = jasmine.createSpyObj('log', ['info', 'error']);
sut = new ExampleSystemUnderTest(log);
});
it('should do', () => {
// ACT
sut.doIt();
// ASSERT
expect(asSpy(log.error)).not.toHaveBeenCalled();
expect(asSpy(log.info)).toHaveBeenCalledTimes(1);
expect(asSpy(log.info).calls.allArgs()).toEqual([
['done']
]);
});
});
Angular 9
Using jasmine.createSpyObj is ideal when testing a component where a simple service is injected. For example: let's say, in my HomeComponent I have a HomeService (injected). The only method in the HomeService is getAddress().
When creating the HomeComponent test suite, I can initialize the component and service as:
describe('Home Component', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
let element: DebugElement;
let homeServiceSpy: any;
let homeService: any;
beforeEach(async(() => {
homeServiceSpy = jasmine.createSpyObj('HomeService', ['getAddress']);
TestBed.configureTestingModule({
declarations: [HomeComponent],
providers: [{ provide: HomeService, useValue: homeServiceSpy }]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
element = fixture.debugElement;
homeService = TestBed.get(HomeService);
fixture.detectChanges();
});
}));
it('should be created', () => {
expect(component).toBeTruthy();
});
it("should display home address", () => {
homeService.getAddress.and.returnValue(of('1221 Hub Street'));
fixture.detectChanges();
const address = element.queryAll(By.css(".address"));
expect(address[0].nativeNode.innerText).toEqual('1221 Hub Street');
});
});
This is a simple way to test your component using jasmine.createSpyObj. However, if your service has more methods more complex logic, I would recommend creating a mockService instead of createSpyObj. For example:
providers: [{ provide: HomeService, useValue: MockHomeService }]
Hope this helps!
Building on #Eric Swanson's answer, I've created a better readable and documented function for using in my tests. I also added some type safety by typing the parameter as a function.
I would recommend to place this code somewhere in a common test class, so that you can import it in every test file that needs it.
/**
* Transforms the given method into a jasmine spy so that jasmine functions
* can be called on this method without Typescript throwing an error
*
* #example
* `asSpy(translator.getDefaultLang).and.returnValue(null);`
* is equal to
* `(translator.getDefaultLang as jasmine.Spy).and.returnValue(null);`
*
* This function will be mostly used in combination with `jasmine.createSpyObj`, when you want
* to add custom behavior to a by jasmine created method
* #example
* `const translator: TranslateService = jasmine.createSpyObj('TranslateService', ['getDefaultLang'])
* asSpy(translator.getDefaultLang).and.returnValue(null);`
*
* #param {() => any} method - The method that should be types as a jasmine Spy
* #returns {jasmine.Spy} - The newly typed method
*/
export function asSpy(method: () => any): jasmine.Spy {
return method as jasmine.Spy;
}
Usage would be as follows:
import {asSpy} from "location/to/the/method";
const translator: TranslateService = jasmine.createSpyObj('TranslateService', ['getDefaultLang']);
asSpy(translator.getDefaultLang).and.returnValue(null);

Categories