Created
April 15, 2017 20:50
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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