Skip to content

Instantly share code, notes, and snippets.

@rpl
Created April 15, 2017 20:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rpl/6798d4e6ccb1bdacd3d96fb957fd38fc to your computer and use it in GitHub Desktop.
Save rpl/6798d4e6ccb1bdacd3d96fb957fd38fc to your computer and use it in GitHub Desktop.
jscodeship transform to help me porting a bunch of tests based on sinon to jest
// BE CAREFULL: commit any changes first and run the tests, don't trust the robots ;-)
const getSinonCalls = call => ({
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'sinon'
},
property: {
type: 'Identifier',
name: call
}
}
});
const getMockRequireReRequire = () => ({
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'mockRequire'
},
property: {
type: 'Identifier',
name: 'reRequire'
}
}
});
const getCallIdentifier = call => ({
type: 'CallExpression',
callee: {
type: 'Identifier',
name: call
}
});
function transformSinonStubsToJestMocks(j, ast) {
ast.find('CallExpression', getSinonCalls('stub'))
.replaceWith(() => {
return j.callExpression(j.memberExpression(j.identifier('jest'), j.identifier('fn')), []);
});
}
function transformReRequireToJestMocks(j, ast) {
const makeJestMockCall = path => {
return j.callExpression(
j.memberExpression(
j.identifier('jest'), j.identifier('mock'),
),
[
path.value.arguments[0], j.arrowFunctionExpression(
[], path.value.arguments[1]
)
]
);
};
ast.find('CallExpression', getCallIdentifier('mockRequire')).replaceWith(path => {
return makeJestMockCall(path);
});
ast.find('CallExpression', getMockRequireReRequire()).replaceWith(path => {
return j.callExpression(j.identifier('require'), path.value.arguments);
});
}
const getSinonMockCall = call => ({
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
property: {
type: 'Identifier',
name: call
}
}
},
property: {
type: 'Identifier',
name: 'returns'
}
}
});
function transformSinonMockedCallsToJestMocks(j, ast) {
const makeJestMockedCalls = (mockName, args) => {
return j.callExpression(
j.memberExpression(
j.identifier(mockName),
j.identifier('mockReturnValueOnce')
),
args
);
};
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1);
}
ast.find('CallExpression', getSinonMockCall('onFirstCall')).replaceWith(path => {
const mockName = path.value.callee.object.callee.object.name;
return makeJestMockedCalls(mockName, path.value.arguments);
});
ast.find('CallExpression', getSinonMockCall('onSecondCall')).replaceWith(path => {
const mockName = path.value.callee.object.callee.object.name;
return makeJestMockedCalls(mockName, path.value.arguments);
});
ast.find('CallExpression', getSinonMockCall('onCall')).replaceWith(path => {
const mockName = path.value.callee.object.callee.object.name;
return makeJestMockedCalls(mockName, path.value.arguments);
});
ast.find('VariableDeclarator', {type: 'VariableDeclarator'}).forEach(path => {
if (path.value.init && path.value.init.callee &&
path.value.init.callee.object &&
path.value.init.callee.property &&
path.value.init.callee.object.name === 'jest' &&
path.value.init.callee.property.name === 'fn' &&
!path.value.id.name.startsWith('mock')) {
const renamedMock = path.value.id.name;
ast.find('Property', {
type: 'Property',
kind: 'init',
value: {
type: 'Identifier',
name: renamedMock
},
key: {
type: 'Identifier',
name: renamedMock
}
}).replaceWith(() => {
return j.property('init', j.identifier(renamedMock),
j.identifier(`mock${capitalize(renamedMock)}`));
});
ast.findVariableDeclarators(path.value.id.name)
.renameTo(`mock${capitalize(path.value.id.name)}`);
}
});
ast.find('MemberExpression', {
type: 'MemberExpression',
property: {
type: 'Identifier',
name: 'callCount'
}
}).replaceWith(path => {
path.value.property = j.memberExpression(
j.memberExpression(
j.identifier('mock'),
j.identifier('calls')
),
j.identifier('length')
);
return j(path).toSource();
});
ast.find('ExpressionStatement', {
type: 'ExpressionStatement',
expression: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'expect'
},
arguments: [
{
type: 'MemberExpression',
property: {
type: 'Identifier',
name: 'calledOnce'
}
}
]
}
}
}
}).replaceWith(path => {
path.value.expression.arguments[0] = j.literal(1);
path.value.expression.callee.object.arguments[0].property = j.memberExpression(
j.memberExpression(j.identifier('mock'), j.identifier('calls')),
j.identifier('length')
);
return j(path).toSource();
});
ast.find('CallExpression', {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'afterEach'
},
arguments: [
{
body: {
type: 'BlockStatement',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'mockRequire'
},
property: {
type: 'Identifier',
name: 'stopAll'
}
}
}
}
]
}
}
]
}).replaceWith(path => {
path.value.callee.name = 'beforeEach';
path.value.arguments[0].body.body[0].expression.callee.object.name = 'jest';
path.value.arguments[0].body.body[0].expression.callee.property.name = 'resetModules';
return j(path).toSource();
});
const statement = j.template.statement;
const statements = j.template.statements;
ast.find('AwaitExpression', {
type: 'AwaitExpression',
argument: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
property: {
type: 'Identifier',
name: 'toThrowError'
}
}
}
}).forEach(path => {
const exceptionMatch = path.value.argument.arguments;
const exceptionExpression = path.value.argument.callee.object.arguments[0];
const awaitExceptionExpression = j.awaitExpression(exceptionExpression);
j(path.parent).insertAfter(statements`
try {
${awaitExceptionExpression}
} catch (err) {
exception = err;
}
expect(exception && exception.message).toMatch(
${exceptionMatch[0]}
);
`);
}).replaceWith(() => {
return statement`let exception`;
});
ast.find('MemberExpression', {
type: 'MemberExpression',
object: {
type: 'MemberExpression',
property: {
type: 'Identifier',
name: 'firstCall'
}
},
property: {
type: 'Identifier',
name: 'args'
}
}).replaceWith(path => {
const mockVarName = path.value.object.object.name;
return j.memberExpression(
j.memberExpression(
j.memberExpression(j.identifier(mockVarName), j.identifier('mock')),
j.identifier('calls')
),
j.literal(0)
);
});
}
/**
* This replaces every occurence of variable "foo".
*/
module.exports = function (fileInfo, api) {
const j = api.jscodeshift;
const ast = api.jscodeshift(fileInfo.source);
transformSinonStubsToJestMocks(j, ast);
transformReRequireToJestMocks(j, ast);
transformSinonMockedCallsToJestMocks(j, ast);
return ast.toSource();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment