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.
Related
How might a Flow.js interface be mocked with Jest? To my surprise, I haven't found this issue addressed anywhere.
I'm fairly new to both, but the only (untested) option I see is to create a class that inherits from the interface and then mock the implementing class. This seems quite cumbersome and I don't believe I could place the implementing classes (which are what would actually be mocked) inside the __mocks__ folders expected by Jest and still get the expected behavior.
Any suggestions? Is there a more appropriate mocking tool?
Update
Why do I want to create a mock for an interface? This code intends to have a clean separation of the domain and implementation layers, with the domain classes using Flow interfaces for all injected dependencies. I want to test these domain classes. Using a mocking tool could ideally allow me to more easily and expressively modify the behavior of the mocked services and confirm that the domain class being tested is making the appropriate calls to these mocked services.
Here's a simplified example of a class that I would be testing in this scenario. UpdateResources would be the class under test, while ResourceServer and ResourceRepository are interfaces for services that I would like to mock and 'spy' upon:
// #flow
import type { ResourceServer } from '../ResourceServer';
import type { ResourceRepository } from '../ResourceRepository';
/**
* Use case for updating resources
*/
export default class UpdateResources {
resourceServer: ResourceServer;
resourceRepository: ResourceRepository;
constructor(resourceServer: ResourceServer, resourceRepository: ResourceRepository) {
this.resourceServer = resourceServer;
this.resourceRepository = resourceRepository;
}
async execute(): Promise<boolean> {
const updatesAvailable = await this.resourceServer.checkForUpdates();
if (updatesAvailable) {
const resources = await this.resourceServer.getResources();
await this.resourceRepository.saveAll(resources);
}
return updatesAvailable;
}
}
A solution
The approach I've arrived at which seems to work quite well for my purposes is to create a mock implementation of the interface in the __mocks__ directory what exposes jest.fn objects for all implemented methods. I then instantiate these mock implementations with new and skip any use of jest.mock().
__mocks__/MockResourceServer.js
import type { ResourceServer } from '../ResourceServer';
export default class MockResourceServer implements ResourceServer {
getResources = jest.fn(() => Promise.resolve({}));
checkForUpodates = jest.fn(() => Promise.resolve(true));
}
__mocks__/MockResourceRepository.js
import type { ResourceRepository } from '../ResourceRepository';
export default class MockResourceRepository implements ResourceRepository {
saveAll = jest.fn(() => Promise.resolve());
}
__tests__/UpdateResources.test.js
import UpdateResources from '../UpdateResources';
import MockResourceRepository from '../../__mocks__/MockResourceRepository';
import MockResourceServer from '../../__mocks__/MockResourceServer';
describe('UpdateResources', () => {
describe('execute()', () => {
const mockResourceServer = new MockResourceServer();
const mockResourceRepository = new MockResourceRepository();
beforeEach(() => {
jest.clearAllMocks();
});
it('should check the ResourceServer for updates', async () => {
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceServer.checkForUpdates).toHaveBeenCalledTimes(1);
});
it('should save to ResourceRepository if updates are available', async () => {
mockResourceServer.load.mockResolvedValue(true);
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceRepository.saveAll).toHaveBeenCalledTimes(1);
});
it('should NOT save to ResourceRepository if NO updates are available', async () => {
mockResourceServer.load.mockResolvedValue(false);
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceRepository.saveAll).not.toHaveBeenCalled();
});
});
});
If anyone can offer any improvements, I'm open!
The thing is, you don't actually need to mock an implementation of an interface. The purpose of a mock is to 'look like' the real thing, but if you already have an interface that says what the real thing should look like, any implementation that conforms to the interface will automatically serve equally well as a mock. In fact, from the point of view of the typechecker, there won't be a different between the 'real' and the 'mock' implementation.
Personally what I like to do is to create a mock implementation that can be constructed by feeding it mock responses. Then it can be reused in any test case by constructing it directly in that test case with the exact responses it should provide. I.e., you 'script' the mock with what it should say by injecting the responses at the time of construction. The difference between it and your mocking implementation is that if it doesn't have a response, it throws a exception and fails the test. Here's an article I wrote that shows this method: https://dev.to/yawaramin/interfaces-for-scaling-and-testing-javascript-1daj
With this technique, a test case might look like this:
it('should save to ResourceRepository if updates are available', async () => {
const updateResources = new UpdateResources(
new MockResourceServer({
checkForUpdates: [true],
getResources: [{}],
}),
new MockResourceRepository({
saveAll: [undefined],
}),
);
const result = await updateResources.execute();
expect(result).toBeTruthy();
});
What I like about these mocks is that all the responses are explicit, and show you the sequence of calls that are happening.
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))
I have the next code i made a test but now i have a dilema strictly speaking with tdd for add
"this.loadCounter('anotherReq', 'anotherError', differentCallback);"
i have to reply the tests only for test the behavior but i not sure if this is necessary.
class Statistic extends PureComponent<Props, State> {
state = {
};
componentDidMount() {
this.loadCounter('suggestedReqCount', 'hasSuggestedReqsErrors', getCountSuggestedReqs);
this.loadCounter('anotherReq', 'anotherError', differentCallback);
}
loadCounter = async (
stateCounterKey: string, stateCounterErrorKey: string, loaderFunction: Function) => {
try {
const count = await loaderFunction();
this.setState({
[stateCounterKey]: count,
});
} catch (ex) {
this.setState({
[stateCounterErrorKey]: true,
});
}
}
}
this are the test that i have
test('Should set state with suggested requirement count', async () => {
const wrapper = mount(
<Statistic
intl={{
formatMessage: jest.fn(),
}}
/>,
);
wrapper.update();
await getCountSuggestedReqs();
expect(wrapper.state().suggestedReqCount).toBe(5);
});
test('Should on fail load suggested reqs update state', async () => {
getCountSuggestedReqs.mockReturnValueOnce(Promise.reject('Error creado'));
const wrapper = mount(
<Statistic
intl={{
formatMessage: jest.fn(),
}}
/>,
);
wrapper.update();
await getCountSuggestedReqs();
expect(wrapper.state().hasSuggestedReqsErrors).toBe(true);
});
The first rule of TDD is that we cannot write any production code before having a failing test.
This alone implies that if you want to add production code, you have to write a test that will require it.
That said, there are cases where I won't write a unit test and let the larger tests grab that functionality, like in cases of simple delegation - the code isn't doing any logic, just passing the same arguments to another function, so there's nothing really to test, and the wiring will be tested from the outside.
In this case, however, it seems to me that you're invoking that function twice in order to get different information.
You could expand the existing test to include the new data, but I'm not a fan of that option. I would rather leave existing tests and add new tests for new functionality. Notice that "As the tests become more specific, the code becomes more generic", is true here too. Perhaps when the third call to loadCounter comes in, you'll need to refactor the code to be more generic, but at this point I would just add the second unit test.
All that said, you should definitely separate your logic code and your component code. There's no reason this unit test would be an Enzyme test. The code in componentDidMount could be easily refactored to a function that could be unit tested by just a plain ol' test.
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
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);
});
});