gemini-testing / testplane

Testplane (ex-hermione) browser test runner based on mocha and wdio
https://testplane.io
MIT License
720 stars 63 forks source link

feat(component-testing): implement mocks #1027

Closed DudaGod closed 1 week ago

DudaGod commented 1 month ago

What is done:

Implement ability to mock modules and use spies in component testing. The same as in jest.

How it works

I'll explain it with a simple example:

// tests/test.testplane.tsx
import { render } from '@testing-library/react';
import App from '../App';
// testplane/mock - new file which is exports methods from `@vitest/spy` and custom methods: `mock`, `unmock`. 
// These methods works only in browser env;
import {fn, mock} from "testplane/mock"; 
import { handleClick } from './utils';

mock('./utils', () => ({
    handleClick: fn().mockImplementationOnce(() => {
        console.log('Handle click by mock');
    })
}));

it('should render react button', async ({browser}) => {
    render(
        <App />
    );

    await browser.$('input').click();

    expect(handleClick).toHaveBeenCalledTimes(1);
});
// App.tsx
import {handleClick} from "./tests/utils";

export default function App() {
    return (
        <div id="root">
            <input
                type="button"
                value="Some button"
                onClick={handleClick}
            />
        </div >
    );
}
// utils.ts
export const handleClick = (): void => {
    console.log('Handle click by user code');
}

In order to correctly mock handleClick method from utils.ts source code of test file and dependencies which used mocked module modified in runtime. Test file will looks like:

// tests/test.testplane.tsx
import * as __testplane_import_1__ from './utils';
import {fn, mock} from "testplane/mock";

await mock('./utils', () => ({
    handleClick: fn().mockImplementationOnce(() => {
        console.log('Handle click by mock');
    })
}), __testplane_import_1__);

const { render } = await import('@testing-library/react');
const App = await import('../App');
const { handleClick } = importWithMock('./utils', __testplane_import_1__);

// ...

What happens:

Component file will looks like (after modify in runtime):

import * as __testplane_import_2__ from './tests/utils';
const {handleClick} = importWithMock("./tests/utils", __testplane_import_2__);

export default function App() {
    // ...
}

What happens:

Discuss: