Sinon spy for named exports in ES6 - unit-testing

I use enzyme with sinon for unit testing React components. Normally, when it comes to testing instance methods I just spy on the method on instance of the component and assert respectively.
However, I have this global function that I use in many components of the app, which is a named export. sinon throws if I try to spy on that.
import { openModel } from '../global/handlers/';
<Block
onRemove={(data) => openModal(...args)}
/>
So, currently I am calling prop method onRemove to assert that openModal gets called with the arguments but I can't really spy on the exported method i.e. openModal.
I understand that I need to provide a context to this function to be able to spy on the underlying function but I am not really sure what's the preferred way of doing something like this.
PS: I would be happy to provide more details if need be.

If you are using webpack to build your test code, then you can use inject-loader to replace the imported module with a stub:
describe('Your component', () => {
let openModalSpy;
let Component;
// Use whatever the path to your component is
const injectImports = require('inject-loader!components/Component');
beforeEach(() => {
openModalSpy = sinon.spy();
Component = injectImports({
openModal: openModalSpy
}).default;
})
it('calls open modal with data argument', () => {
const wrapper = shallow(
<Component />
);
// Do something that will result in openModal being called
expect(openModalSpy).to.have.been.calledWith({
// some data
});
}
}

Related

How to spyOn window.location.assign on Vuejs Application with Jest?

I am needing to spyOn window.location.assign for my unit test. But when I run the test I get this error.
Cannot spy the assign property because it is not a function; undefined given instead
Here is my code:
jest.spyOn(window.location, "assign");
Could anyone give me some hints or solutions on this case?
Since Jest v25 (Which uses a newer version of JSDOM) you will get the following error:
TypeError: Cannot assign to read only property 'assign' of object '[object Location]'
This is not a Jest/JSDOM bug by the way. This is normal browser behaviour and JSDOM tries to act like a real browser.
A workaround is to remove the location object, create your own one and after running your tests you should reset it to the original location object:
describe('My awesome unit test', () => {
// we need to save the original object for later to not affect tests from other files
const realLocation = global.location
beforeAll(() => {
delete global.location
global.location = { assign: jest.fn() }
// or even like this if you are also using other location properties (or if TypeScript complains):
// global.location = { ...realLocation, assign: jest.fn() }
})
afterAll(() => {
global.location = realLocation
})
it('should call location.assign', () => {
// ...your test code
expect(global.location.assign).toHaveBeenCalled()
// or even better:
// expect(global.location.assign).toHaveBeenCalledWith('/my_link')
})
})
As window can only be accessed through the global keyword in jest tests and window.location.assign is not implemented in jsdom, you can try
jest
.spyOn(global.location, "assign")
.mockImplementation(url => console.log(url))

Mocking BsModalRef for Unit Testing

I am using the BsModalRef for showing modals and sending data using the content property. So we have some like this :
this.followerService.getFollowers(this.bsModalRef.content.channelId).subscribe((followers) => {
this.followerList = followers;
this.followerList.forEach((follower) => {
follower.avatarLink = this.setUserImage(follower.userId);
this.followerEmails.push(follower.email);
});
});
We are setting the channelId in content of bsModalRef (this.bsModalRef.content.channelId). It is working fine. Now i am writing a unit test for this. Problem is i am not able to mock it. I have tried overriding, spy etc but nothing seems to work. I am using the approach mentioned in this link. One alternative is to use TestBed but i am not much aware of its use. Can anyone please help me finding any approach by which this can be achieved ?
I recently had to do something similar and Mocking the method call worked. The tricky part is injecting the BsModalService in both the test suite and the component.
describe('MyFollowerService', () => {
configureTestSuite(() => {
TestBed.configureTestingModule({
imports: [...],
declarations: [...],
providers: [...]
}).compileComponents();
});
// inject the service
beforeEach(() => {
bsModalService = getTestBed().get(BsModalService);
}
it('test', () => {
// Mock the method call
bsModalService.show = (): BsModalRef => {
return {hide: null, content: {channelId: 123}, setClass: null};
};
// Do the test that calls the modal
});
});
As long as you're calling bsModal as follows this approach will work
let bsModalRef = this.modalService.show(MyChannelModalComponent));
Finally, here are some links that have more indepth coverage about setting up the tests with TestBed.
https://chariotsolutions.com/blog/post/testing-angular-2-0-x-services-http-jasmine-karma/
http://angulartestingquickstart.com/
https://angular.io/guide/testing

React Native: Jest mocking Platform

When writing unit tests for a React Native project I want to be able to test different snapshots based on different platforms.
I first tried jest.mock to mock Platform but seems to be async. This approach does work when I have two separate files, but I'd prefer to keep everything in one file if possible.
I tried jest.doMock because of this snippet from the documentation:
When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
https://facebook.github.io/jest/docs/en/jest-object.html#jestdomockmodulename-factory-options
However I'm still seeing undesirable results. When I console.log in the android test I see that Platform.OS is whatever I set the first doMock to be.
I also tried wrapping the mock in a beforeEach in a describe becasue I thought that might help with scoping
http://facebook.github.io/jest/docs/en/setup-teardown.html#scoping
describe('ios test', () => {
it('renders ui correctly', () => {
jest.doMock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'ios';
return Platform;
});
const wrapper = shallow(<SomeComponent />);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('android test', () => {
it('renders ui correctly', () => {
jest.doMock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'android';
return Platform;
});
const wrapper = shallow(<SomeComponent />);
const tree = renderer.create(wrapper).toJSON();
expect(tree).toMatchSnapshot();
});
});
Any ideas on how I can change the mock Platform for tests in the same file?
There are a lot of suggestions on how to solve this problem in another question, but none of them worked for me either, given the same requirements you have (tests for different OSs in the same suite file and in one test run).
I eventually worked around it with a somewhat clunky trivial helper function that can be mocked as expected in tests – something like:
export function getOS() {
return Platform.OS;
}
Use it instead of Platform.OS in your code, and then simply mock it in your tests, e.g.
it('does something on Android', () => {
helpers.getOS = jest.fn().mockImplementationOnce(() => 'android');
// ...
}
That did the trick; credit for the idea is due to this guy.

How to properly unit test a React component?

When I test a react component, what are the best practices and what things should I test for? In normal tests I usually just test if the correct state+input leads to the correct state+output
But React components are a bit different. They have state+props+userInput which result in state+markup.
This can lead to many, many different potential scenarios. Do I need to test for the resulting state of all those scenarios?
The markup can be huge. Should I test if the whole markup-tree is as expected? Or just part of it? How do I determine what part of the markup to test?
First obvious things to keep in mind:
If the logic of the your components can be encapsulated into modules and tested independently, then do it. Example: For a calculator component, the calculations themselves can be tested independently of the component. I know this is obvious, but just to make the point.
Break your components apart into smaller ones and test each of them granularly.
Regarding the component, always test:
If the correct props will render the correct output (HTML).
If the correct user interaction (click, key presses...) will fire the appropriate events and lead to the correct output (HTML). I usually don't deal with the component state at all during unit tests, as I don't find this a good practice. To test a TV you shouldn't have to open it.
If you are not sure about what library to use to test React components, I'd strongly recommend Enzyme.
Examples from their GitHub page:
describe('<MyComponent />', () => {
it('renders three <Foo /> components', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find(Foo)).to.have.length(3);
});
it('renders an `.icon-star`', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find('.icon-star')).to.have.length(1);
});
it('renders children when passed in', () => {
const wrapper = shallow(
<MyComponent>
<div className="unique" />
</MyComponent>
);
expect(wrapper.contains(<div className="unique" />)).to.equal(true);
});
it('simulates click events', () => {
const onButtonClick = sinon.spy();
const wrapper = shallow(
<Foo onButtonClick={onButtonClick} />
);
wrapper.find('button').simulate('click');
expect(onButtonClick).to.have.property('callCount', 1);
});
});

On console.log, ViewChild variable is undefined in unit test

I'm trying to test a component that has an #ViewChild annotation. One of the functions that I'm trying to test calls the #ViewChild's element for focus. However, when I try to log out the #ViewChild variable, it is always undefined. I thought componentFixture.detectChanges() would initiate the ElementRef, but it doesn't seem to.
Is there any way to make it so it isn't undefined?
You didn't show your code but, probably u have that undefined because you did your ViewChild like:
#ViewChild(MySubComponent)
instead of
#ViewChild('componentref')
and then in your template:
<my-sub-component #componentref></my-sub-component>
and of course you need to init your component with componentFixture.detectChanges()
I don't know which version of Angular2 you use and how you initialize your test suite but the detectChanges method on the ComponentFixture instance is responsible to set such fields.
Here is a sample test that shows this:
it('should set testElt', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(MyList).then((componentFixture: ComponentFixture) => {
expect(componentFixture.componentInstance.testElt).toBeUndefined();
componentFixture.detectChanges();
expect(componentFixture.componentInstance.testElt).toBeDefined();
var testElt = componentFixture.componentInstance.testElt;
expect(testElt.nativeElement.textContent).toEqual('Some test');
});
}));
See the corresponding plunkr: https://plnkr.co/edit/THMBXX?p=preview.