How to test a function from a svelte component with jest? - jestjs

There is a lot of documentation on the internet to test svelte component with jest, calling render functions and simulating browser events. This is nice, but how can I test a function inside a svelte component?
mycompoment.svelte
<script>
function veryComplicated(foo) {
...
}
</script>
<div>...</div>
mycomponent.test.js
import { veryComplicated } from "./mycomponent.svelte"
test('it works', async () => {
expect(vercomplicated("foo").toBe("bar"))
})
jest
FAIL src/mycomponent.test.ts
● Test suite failed to run
src/mycomponent.test.ts:1:10 - error TS2614: Module '"*.svelte"' has no exported member 'veryComplicated'. Did you mean to use 'import veryComplicated from "*.svelte"' instead?
1 import { veryComplicated } from "./mycomponent.svelte"
~~~~~~~~~~~~~~~
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 1.697 s
Ran all test suites.
Adding export before the veryComplicated definition does not help.
How can I test the veryComplicated function?

You can export a function using a module context script block.
<script context="module">
export veryComplicated() {
// ...
}
</script>
<div>...</div>
Then you can do import { veryComplicated } from './mycomponent.svelte' as you were originally trying to do.
https://svelte.dev/tutorial/module-exports

Found it. I had to call render
mycomponent.test.js
import { render } from '#testing-library/svelte'
import MyComponent from "./mycomponent.svelte"
test('it works', async () => {
const component = render(MyComponent)
expect(component.verComplicated("foo").toBe("bar")
})
And it is needed to export the veryComplicated function.

Related

I get an error when testing with jest on code that imports a homegrown library

I get an error when testing with jest on code that imports a homegrown library.
Other than jest, the other processes are successful, how can I make the test succeed?
FAIL src/pages/e2e.spec.tsx
● Test suite failed to run
Cannot find module 'components/layout/Layout' from 'src/pages/index.tsx'
Require stack:
src/pages/index.tsx
src/pages/e2e.spec.tsx
> 1 | import { Layout } from 'components/layout/Layout'
| ^
2 | import { HomepageComponent } from 'components/parts/home'
3 | import { useSearchMedicines } from 'hooks/api/product/useSearchMedicines'
4 | import { useRouter } from 'next/router'
at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:427:11)
at Object.<anonymous> (src/pages/index.tsx:1:1)
at Object.<anonymous> (src/pages/e2e.spec.tsx:7:1)
try
commnad:
npm test
code
import { render, screen } from '#testing-library/react'
import userEvent from '#testing-library/user-event'
import { Layout } from 'components/layout/Layout'
import styled from '#emotion/styled'
import { Header } from 'components/parts/common/header'
import { useTranslationText } from 'locale'
import Home from './index'
describe('Home', () => {
it('should display search results', async () => {
// Render the Home component
render(<Home />)
// Search for a new keyword
const searchInput = screen.getByRole('textbox')
userEvent.clear(searchInput)
userEvent.type(searchInput, 'new keyword')
userEvent.click(screen.getByRole('button'))
// Wait for new search results to load
const newMedicineNames = await screen.findAllByTestId('medicine-name')
expect(newMedicineNames.length).greaterThan(0)
expect(newMedicineNames[0]).not.equal(medicineNames[0])
})
})
expect
test success
Test Suites: 0 failed, 1 passed, 1 total
Tests: 0 failed, 1 passed, 1 total

ignore setup mock for one file

I have a decorator that I use on a lot of my methods. To not have to mock it each time, I have added a mock on jest.setup.js:
jest.mock('src/something', () => {
someMethod: jest.fn.mockImplementation(/*some implementation*/)
})
This works fine, but now I want to unit test this one method (the someMethod in this example) and I can't, since it brings up the mock. How can I ignore this mock for only this file/test?
You can use jest.unmock(moduleName) in the test file of a module. So that your module under test will use the real module rather than the mocked version.
Let's see an example:
./src/stackoverflow/73129547/something.ts:
export const someMethod = () => 1;
This is the module we want to mock.
./jest.setup.js:
jest.setTimeout(5 * 1000);
jest.mock('./stackoverflow/73129547/something', () => ({
someMethod: jest.fn().mockReturnValue(0),
}));
jest.config.js:
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['./jest.setup.js'],
};
We set up the mock using jest.mock() in the jest.setup.js file. So that every module under test will use the mocked version of this module.
Suppose our project has two modules: a.ts and b.ts, they use the someMethod exported from the something module.
./src/stackoverflow/73129547/a.ts:
import { someMethod } from './something';
export const a = someMethod;
./src/stackoverflow/73129547/b.ts:
import { someMethod } from './something';
export const b = someMethod;
The test suites of a and b modules:
./src/stackoverflow/73129547/a.test.ts:
import { a } from './a';
describe('73129547 - a', () => {
test('should pass', () => {
expect(a()).toBe(0);
});
});
./src/stackoverflow/73129547/b.test.ts:
import { b } from "./b";
describe('73129547 - b', () => {
test('should pass', () => {
expect(b()).toBe(0);
});
});
Test result:
PASS stackoverflow/73129547/b.test.ts
PASS stackoverflow/73129547/a.test.ts (9.829 s)
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 10.681 s
As you can see the expected execution result. Both the a and b modules use the mocked someMethod which has a mock return value: 0.
Now, we want to test the someMethod of the something module, we should test the real someMethod rather than the mocked one. Test mock implementations make no sense.
./src/stackoverflow/73129547/something.test.ts:
import { someMethod } from './something';
jest.unmock('./something');
describe('73129547 - something', () => {
test('should pass', () => {
expect(someMethod()).toBe(1);
});
});
Test result:
PASS stackoverflow/73129547/a.test.ts
PASS stackoverflow/73129547/b.test.ts
PASS stackoverflow/73129547/something.test.ts
Test Suites: 3 passed, 3 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.955 s, estimated 14 s
As you can see, the return value of real someMethod is 1. jest.unmock(moduleName) will unmock the module and return the real module.

Create React App and JEST property matchers

I've got this component that I've simplified for this example as part of a bigger create react app
import React from 'react';
function myVersion() {
return (
<p className={'ActivateSound--para'} data-testid={`version`}>
Version {process.env.REACT_APP_VERSION} has loaded, congrats!
</p>
);
}
export { myVersion };
and in the env I get the version from package.json
REACT_APP_VERSION=$npm_package_version
but in the test I've got
import React from 'react';
import { myVersion } from '../components/my-version';
import { render, screen, cleanup, fireEvent } from '#testing-library/react';
afterEach(cleanup);
describe('my version component', () => {
test('snapshot hasnt changed', () => {
expect(container.firstChild).toMatchSnapshot();
});
});
I am now looking at https://jestjs.io/docs/snapshot-testing#property-matchers. It seems I can exclude the version number somehow, I've tried a bunch of attempts but it's not ignoring the version number
expect(container.firstChild).toMatchSnapshot({
screen.getByTestId('version'): expect.any(String)
});

Jest spiedOn method is not called, but called once change the test case order

The Spied on code is not called when the test is at the second position. (or a second render) and test case fails
Test case passes if the test case is at the first position(or on first render).
Using a very basic create-react-app OOTB example and simplifying it even more for a MCVE:
MyModule.js
import React from 'react';
import someClass from './someClass';
function App() {
someClass.track("someevent");
return null;
}
export default App;
someClass.js
class SomeClass {
constructor() {
this.someProp = null;
}
getSatellite() {
return {
track: () => {}
};
}
track(someProp) {
///THIS BELOW IF CLAUSE IS THE PROBLEM
if (this.someProp === someProp) {
return;
} else {
this.someProp = someProp;
}
///////////////////////
this.getSatellite().track('someevent');
}
}
const instance = new SomeClass();
export default instance;
App.js
import React from 'react';
import MyModule from './MyModule'
function App() {
return (
<div className="App">
<MyModule />
</div>
);
}
export default App;
App.test.js
import React from 'react';
import { render } from '#testing-library/react';
import App from './App';
import someClass from './someClass';
test('renders learn react link', () => {
render(<App />);
});
// it works if this test case is first one, weird :-|
test('renders class', () => {
const track = jest.fn();
jest.spyOn(someClass, 'getSatellite').mockImplementation(()=>{
console.log('here i am');
return {
track
}
})
render(<App />);
expect(track).toHaveBeenCalledTimes(1);
});
Output:
✓ renders learn react link (17ms)
✕ renders class (5ms)
● renders class
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
18 | })
19 | render(<App />);
> 20 | expect(track).toHaveBeenCalledTimes(1);
| ^
21 | });
22 |
at Object.<anonymous> (src/App.test.js:20:17)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 10.472s
Let me know if you need anything in the comments.
In the first render(<App />) the someProp of someClass is being set as someevent.
Now in the next render I am only mocking the function call, but not resetting the someProp. That's why the if (this.someProp === someProp) is taking effect.
So I need to reset the someProp to another value or null and it will work fine.

Test callback prop with Enzyme

I have React-Spring animation in my component:
<SpinnerKf state={status} onRest={changeView && status === 'SUCCESS' ? () => changeView(VIEW_MODES.RECEIPT) : null}>
....
</SpinnerKf>
Where I pass function call inside onRest prop - this is the prop from React-Spring Keyframe, which is called after animation end.
How can I cover this with a test? I'm opened for any tricks, just need to avoid complaining in test coverage.
You can use Enzyme to get the SpinnerKf component and then call its onRest property directly.
Here is a simplified example:
code.js
import * as React from 'react';
const SpinnerKf = () => null;
export const Component = () => (<SpinnerKf onRest={() => { return 'does something'; }}/>);
code.test.js
import * as React from 'react';
import { shallow } from 'enzyme';
import { Component } from './code';
test('callback', () => {
const wrapper = shallow(<Component />);
const result = wrapper.find('SpinnerKf').props().onRest();
expect(result).toBe('does something'); // Success!
});
Note that testing the return value or behavior of the callback is optional, as long as it runs during a unit test it will be included in the code coverage report.

Resources