Mocking window. matchMedia with Chakra´s useBreakpointValue - jestjs

I have just upgraded to chakra 2.2.1 and I am experiencing some problems with running tests with Jest and React Testing Library
It seems like it's the useBreakpointValue hook that causes this issue:
TypeError: Cannot read property 'details' of undefined
81 | base: aspectRatios[aspectRatioKey]?.mobile,
82 | lg: aspectRatios[aspectRatioKey]?.desktop,
> 83 | });
Up until now, I have been successfully been using this mock suggested in the jest-documentation, but after upgrading Chakra to 2.2.1 it does not work anymore.
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

I had the same error with useBreakpointValue from chakra UI. I was able to solve this by wrapping the component to be tested with ChakraProvider.
const { container } = render(
<ChakraProvider>
<ComponentToBeTested />
</ChakraProvider>
);

Related

Astro: How to proxy service calls

I am setting up an Astro site which will display data fetched from a simple service running on the same host but a different port.
The service is a simple Express app.
server.js:
const express = require('express')
const app = express()
const port = 3010
const response = {
message: "hello"
}
app.get('/api/all', (_req, res) => {
res.send(JSON.stringify(response))
})
app.listen(port, () => {
console.log(`listening on port ${port}`)
})
Since the service is running on port 3010, which is different from the Astro site, I configure a server proxy at the Vite level.
astro.config.mjs:
import { defineConfig } from 'astro/config';
import react from '#astrojs/react';
export default defineConfig({
integrations: [react()],
vite: {
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
},
server: {
proxy: {
'/api/all': 'http://localhost:3010'
}
}
},
});
Here is where I am trying to invoke the service.
index.astro:
---
const response = await fetch('/api/all');
const data = await response.json();
console.log(data);
---
When I run yarn dev I get this console output:
Response {
size: 0,
[Symbol(Body internals)]: {
body: Readable {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
_read: [Function (anonymous)],
[Symbol(kCapture)]: false
},
stream: Readable {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
_read: [Function (anonymous)],
[Symbol(kCapture)]: false
},
boundary: null,
disturbed: false,
error: null
},
[Symbol(Response internals)]: {
type: 'default',
url: undefined,
status: 404,
statusText: '',
headers: { date: 'Tue, 02 Aug 2022 19:41:02 GMT' },
counter: undefined,
highWaterMark: undefined
}
}
It looks like the network request is returning a 404.
I'm not seeing in the doc much more about server configuration.
Am I going about this the right way?
I have this working correctly with a vanilla Vite app and the same config/setup.
How can I proxy local service calls for an Astro application?
Short Answer
You cannot proxy service calls with Astro but also you don't have to
For direct resolution answer see section functional test without proxy
Details
Astro does not forward the server.proxy config to Vite (unless you patch your own version of Astro), the Astro Vite server config can be seen empty
proxy: {
// add proxies here
},
reference https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L125
there is a merge of Astro server with Astro vite.server config but it does not take the proxy param. This is not obvious to get from the code, see tests later.
let result = commonConfig;
result = vite.mergeConfig(result, settings.config.vite || {});
result = vite.mergeConfig(result, commandConfig);
reference https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L167
Tests
Config tests
I tried all possible combinations of how to input config to Astro and in each location a different port number to show which one takes an override
a vite.config.js file on root with
export default {
server: {
port:6000,
proxy: {
'/api': 'http://localhost:4000'
}
}
}
in two locations in the root file astro.config.mjs
server
vite.server
export default defineConfig({
server:{
port: 3000,
proxy: {
'/api': 'http://localhost:4000'
}
},
integrations: [int_test()],
vite: {
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
},
server: {
port:5000,
proxy: {
'/api': 'http://localhost:4000'
}
}
}
});
in an Astro integration
Astro has a so called integration that helps update the config (sort of Astro plugins) the integration helps identify what was finally kept in the config and also gives a last chance to update the config
integration-test.js
async function config_setup({ updateConfig, config, addPageExtension, command }) {
green_log(`astro:config:setup> running (${command})`)
updateConfig({
server:{proxy : {'/api': 'http://localhost:4000'}},
vite:{server:{proxy : {'/api': 'http://localhost:4000'}}}
})
console.log(config.server)
console.log(config.vite)
green_log(`astro:config:setup> end`)
}
this is the output log
astro:config:setup> running (dev)
{ host: false, port: 3000, streaming: true }
{
optimizeDeps: { esbuildOptions: { define: [Object] } },
server: { port: 5000, proxy: { '/api': 'http://localhost:4000' } }
}
astro:config:setup> end
the proxy parameter is removed from astro server config, the vite config is visible but has no effect as it is overridden, and not forwarded to Vite
test results
dev server runs on port 3000 which is from Astro config server all other configs overridden
the fetch api fails with the error
error Failed to parse URL from /api
File:
D:\dev\astro\astro-examples\24_api-proxy\D:\dev\astro\astro-examples\24_api-proxy\src\pages\index.astro:15:20
Stacktrace:
TypeError: Failed to parse URL from /api
at Object.fetch (node:internal/deps/undici/undici:11118:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
functional test without proxy
Given that Astro front matter runs on the server side, in SSG mode during build and in SSR mode on page load on the server then the server sends the result html, Astro has access to all host ports and can directly use the service port like this
const response = await fetch('http://localhost:4000/api');
const data = await response.json();
console.log(data);
The code above runs as expected without errors
Reference Example
All tests and files mentioned above are available on the reference example github repo : https://github.com/MicroWebStacks/astro-examples/tree/main/24_api-proxy
You can add your own proxy middleware with the astro:server:setup hook.
For example use http-proxy-middleware in the server setup hook.
// plugins/proxy-middleware.mjs
import { createProxyMiddleware } from "http-proxy-middleware"
export default (context, options) => {
const apiProxy = createProxyMiddleware(context, options)
return {
name: 'proxy',
hooks: {
'astro:server:setup': ({ server }) => {
server.middlewares.use(apiProxy)
}
}
}
}
Usage:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import proxyMiddleware from './plugins/proxy-middleware.mjs';
// https://astro.build/config
export default defineConfig({
integrations: [
proxyMiddleware("/api/all", {
target: "http://localhost:3010",
changeOrigin: true,
}),
],
});

Jest+NextJs: You should only use "next/router" on the client side of your app

I'm mocking the next/router dependency in my Jest+React-testing-libray tests as I always have:
import * as nextRouter from 'next/router';
export const routerData = {
pathname: '/users/create',
route: '/users/create',
query: { },
asPath: '/users/create',
isFallback: false,
basePath: '',
isReady: true,
isPreview: false,
isLocaleDomain: false,
events: {},
};
// mock router
jest.mock('next/router');
nextRouter.useRouter.mockImplementation(() => (routerData));
describe('a component that requires next/router, () => ... );
This had been working correctly but after updating to NextJs 12.2.0 I get this warning:
No router instance found.
You should only use "next/router" on the client side of your app.
This warning makes all my tests with the mocked router to fail.
Ideas to fix this?
Well, it appears that this is not related to 12.2.0. Somehow my last version of Next - 12.0.0 - wasn't thrownig this error but other older versions did.
Thanks to bistacos for the response here.
const useRouter = jest.spyOn(require('next/router'), 'useRouter');
useRouter.mockImplementation(() => ({
pathname: '/',
...moreRouterData
}));

Using Spectron to "mock" Electron when testing with Jest

I'm testing library code for an Electron app with Jest. Jest does weird things to require, which is interfering with that Electron needs to do... I think.
Spectron is meant to allow you to access the various Electron bits from within a test framework, by allowing you to create an Electron app via library calls.
Ultimately, I need to be able to mock require('electron') with some real stuff from Electron (like the creation of browser windows), mostly so that various library bits can work as intended.
Here's what it looks like should work:
in package.json:
"jest": {
"moduleNameMapper": {
"^electron$": "<rootDir>/test/mocks/electron.js"
}
}
test/mocks/electron.js:
const Path = require("path")
const Application = require('spectron').Application
const electronPath = Path.join(__dirname, "../../node_modules/electron/dist/Electron.app/Contents/MacOS/Electron")
const app = new Application({ path: electronPath })
module.exports = app.electron
According to the docs, app.electron should give access to the same things as require('electron') does under normal operation.
Some test:
const { BrowserWindow } = require("electron")
test("some test", () => {
const window = new BrowserWindow()
// ...
})
However, this fails because app.electron is undefined, although App itself is defined:
console.log test/mocks/electron.js:58
<ref *1> Application {
host: '127.0.0.1',
port: 9515,
quitTimeout: 1000,
startTimeout: 5000,
waitTimeout: 5000,
connectionRetryCount: 10,
connectionRetryTimeout: 30000,
nodePath: '~/.nvm/versions/node/v13.0.1/bin/node',
path: '~/electron-hello-world/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron',
args: [],
chromeDriverArgs: [],
env: {},
workingDirectory: '~/electron-hello-world',
debuggerAddress: undefined,
chromeDriverLogPath: undefined,
webdriverLogPath: undefined,
webdriverOptions: {},
requireName: 'require',
api: Api { app: [Circular *1], requireName: 'require' },
transferPromiseness: [Function (anonymous)]
}
Not really sure where to go from here. Looking for any solutions

Aerospike Query Error

query = CLIENT.query(NAMESPACE, SET);
stream = query.foreach();
/*
Get list of all avialable keys
*/
stream.on('error', (error) => {
throw error;
});
stream.on('data', (record) => {
console.info('data', record);
console.info('key', record.key.key);
});
stream.on('end', () => {
console.log('done!');
process.exit(0);
});
Receiving error - AerospikeError: Record does not exist in database. May be returned by read, or write with policy Aerospike.policy.exists.UPDATE.
error encountered in promise chain => { [AerospikeError: Record does not exist in database. May be returned by read, or write with policy Aerospike.policy.exists.UPDATE]
name: 'AerospikeError',
code: 2,
command:
QueryCommand {
client:
Client {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
config: [Object],
as_client: AerospikeClient {},
connected: true,
captureStackTraces: false },
args: [ 'sms_data', 'some_set', [Object], undefined ],
captureStackTraces: false,
key: undefined,
stream:
RecordStream {
aborted: false,
client: [Object],
_events: [Object],
_eventsCount: 3 } },
func: 'as_query_parse_records_async',
file: 'src/main/aerospike/aerospike_query.c',
line: 237,
inDoubt: false }
Although the data is present in the namespace and set.
query result :
aql> select * from sms_data.some_set;
+-----------------------------------------------------+----------+
| 0 | name |
+-----------------------------------------------------+----------+
| MAP('{"dummy":[{"x":"dgjasgdj"}], "name":"Vidur"}') | "Khanna" |
+-----------------------------------------------------+----------+
This is a bug in the Aerospike Node.js client (all versions up to and including v3.2.0). The short version is, that this occurs on Query operations, if at least one server node in the cluster does not have any records in the set that you are querying. See issue #253 on GitHub for details. This should get resolved in the next client release.

Jest: How to manually mock a module (ioredis) with a constructor

I'm following the manual mock example from the Jest docs here
I'm attempting to extend this example to my own project and my manual mock of ioredis (mocks/ioredis.js). I'm trying to mock out the ioredis client hget with my own (so I can return test values) but I'm having trouble due to the fact I need to create a redis client with a constructor let client = Redis.new before I have access to the mocked client.hget.
Here is my manual mock of ioredis:
// __mocks__/ioredis.js
/* eslint-env node */
'use strict';
const IORedis = jest.genMockFromModule('ioredis');
const hget = jest.fn()
.mockImplementationOnce(cb => cb(null, '123'))
.mockImplementationOnce(cb => cb(null, '456'));
// this approach does not work, I'm wondering if I have to create the client
IORedis.hget = hget;
module.exports = IORedis;
When I'm testing I can see that ioredis is indeed getting mocked and if I do a console.log(this.client.hget) in my actual module right before use I see this:
{ [Function]
_isMockFunction: true,
getMockImplementation: [Function],
mock: [Getter/Setter],
mockClear: [Function],
mockReset: [Function],
mockReturnValueOnce: [Function],
mockReturnValue: [Function],
mockImplementationOnce: [Function],
mockImplementation: [Function],
mockReturnThis: [Function],
mockRestore: [Function],
_protoImpl:
{ [Function]
_isMockFunction: true,
getMockImplementation: [Function],
mock: [Getter/Setter],
mockClear: [Function],
mockReset: [Function],
mockReturnValueOnce: [Function],
mockReturnValue: [Function],
mockImplementationOnce: [Function],
mockImplementation: [Function],
mockReturnThis: [Function],
mockRestore: [Function] } }
In my actual tests nothing is being returned and if I remove my manual mock hget function I see the same thing in the console log. I'm assuming this problem would exist for any module that would require a constructor (as opposed to the "fs" example) so the answer is probably a generic one.
Turns out the solution was pretty simple. In my ioredis manual mock I just had to do this:
// Original answer
IORedis.prototype.hget = jest.genMockFn();
IORedis.prototype.hget.mockImplementation(function (key, link) {
// return whatever I want here
});
module.exports = IORedis;
// Latest versions of Jest
const IORedis = jest.genMockFromModule("ioredis");
IORedis.prototype.hget = jest.fn((key, link) => {
// return whatever I want here
});
module.exports = IORedis;
If you are using sinon as your stubbing library. You can stub the ioredis.
import { createSandbox } from 'sinon';
import * as ioredis from 'ioredis';
beforeAll(() => {
const sandbox = createSandbox();
sandbox.stub(ioredis.prototype, 'connect').returns(Promise.resolve());
sandbox.stub(ioredis.prototype, 'sendCommand').returns(Promise.resolve());
})
afterAll(() => {
sandbox.restore();
});

Resources