npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

runtime-spies

v1.0.1

Published

Creates unit test harnesses for legacy code

Downloads

2

Readme

The Problem We're trying to solve

When working with Legacy code one of the main problems is we do not have tests. To start refactoring code we better have tests to let us do it with calm. While integration tests are good, unit tests are much better. Integration tests take a long time and a complicated environment (if that's not the case for you - use the integration tests). Unit tests are fast and do not need any special environment. When we code we run our unit tests every 2-3 minutes if not more, and it takes them under a second to complete. Writing unit tests for legcy code isn't easy. One of the problems is that the code is usually entangled in many other pieces of code, which makes harnessing it inot a unit tests very difficult. The problem we're trying to solve here is to help harnessing legacy code into a unit test.

Runtime Spies - Javascript

The Runtime Spies are pieces of code you add to your code (on a side branch - this doesn't go into production) which capture traffic going into and out of the code you want to test (is it one function? A set of function? That's for you to decide). After adding the code you run scenarios in the integration environenment. The Runtime spies capture the traffic and produce a harness for the unit test.

Then you should start refactoring.

Main Features

  • Capture the arguments sent into a function and producing code that will call your functioin with the same arguments
  • Capture data going into and out of global functions and producing code for the harness that will imitate these functions
  • Capture global variables data (also if they change due to global variables) and produce harness code to simulate them
  • When capturing global variables / arguments (same) the tool knows to handle cyclic reference and when it stumbles upon member functions is tracks them

example (see explanation below)

This is taken from one unit test from the project. You need to require RuntimeSpy.js for it to work. GLobal variables:

var helper1 = function (x) {

    globalVar = 2 * x
    return 2 * x
}
var helper2 = function (x) { return 3 * x }
var globalVar = 5
var globalVar2 = { 1: 6, 2: 2 }
var b = { 1: 1, 2: globalVar2 }
globalVar2['3'] = b
globalVar2['4'] = { 1: 4, 12: function (x) { return 5 * x } }

The function we want to harness:

var testFunction = function (A) {
    helper1(21)
    var a = globalVar2['4']['12'](3)
    var result = a+helper1(A) + helper2(A) + globalVar + globalVar2['3']['2']['1'] + globalVar2['4']['12'](4)
    
    return result
}   

We add our code to the function

 
var testFunction = function (A) {
            
    // We've added the following lines to generate the harness
    var mySpy = new RuntimeSpy('mySpy') //the main spy object
    mySpy.setTestFunctionCall("testFunction(A)") //setting how to call the test function
    eval(mySpy.addGlobalVariablesSpies({ A: A, globalVar: globalVar, globalVar2: globalVar2, helper1: helper1, helper2: helper2 }).getCodeToEvalToSpyOnVariables()) //spying on variables
    //end of setup
    
    helper1(21)
    var a = globalVar2['4']['12'](3)
    var result = a+helper1(A) + helper2(A) + globalVar + globalVar2['3']['2']['1'] + globalVar2['4']['12'](4)

    mySpy.addFinalResult(result) //here we tell the spy what is the end result so it can later assert on it
    harness = mySpy.getHarness() //generating the harness

    return result
}

Now we run the function with our code in it. And this is the harness the RuntimeSpy generates:

var myHarness = new Harness('myHarness') //the main object managing the operation
var mockRepositoryData = {} //This is the "database" of the mock functions
mockRepositoryData['globalVar2[\'4\'][\'12\']'] = {input:[[3],[4]],output:[15,20]}
mockRepositoryData['helper1'] = {input:[[21],[5]],output:[42,10]}
//helper1 was called twice. At the first time there was one input, 21, and the output was 42. At the second time it was 5 and 10.
mockRepositoryData['helper2'] = {input:[[5]],output:[15]}
myHarness.setMockRepositoryData(mockRepositoryData)
//load the harness object with this information

A_DB = new Map([['Initial','A = 5']]) //"database" for global variable A. It had one value throughout the program's run: 5
var A
myHarness.addGlobalVariableMock('A',A_DB) //load A to the harness object

globalVar_DB = new Map([['Initial','globalVar = 5'],['helper1_0','globalVar = 42'],['helper1_1','globalVar = 10']])
var globalVar
myHarness.addGlobalVariableMock('globalVar',globalVar_DB)

globalVar2_DB = new Map([['Initial','globalVar2 = {1:6,2:2,3:{1:1},4:{1:4,12:function(){}}};globalVar2[\'3\'][\'2\']=globalVar2']])
var globalVar2
myHarness.addGlobalVariableMock('globalVar2',globalVar2_DB)

myHarness.updateVariablesByTag('Initial',function(codeToEval){eval(codeToEval)})
//Here, the global variables (A, globalVar and globalVar2 are updated to to have the first value (tag == "Initial"))

myHarness.addFunctionMock('globalVar2[\'4\'][\'12\']')
//loading the global function, globalVar2['4']['12'] to the harness object. The below is the definition of the function
globalVar2['4']['12']= function(){
    var returnValue =  myHarness.callFunctionSpy('globalVar2[\'4\'][\'12\']',arguments,function(codeToEval){eval(codeToEval)})
    if (returnValue!='NOVALUERETURNED')return eval(returnValue)
}

myHarness.addFunctionMock('helper1')
helper1= function(){
    var returnValue =  myHarness.callFunctionSpy('helper1',arguments,function(codeToEval){eval(codeToEval)})
    if (returnValue!='NOVALUERETURNED')return eval(returnValue)
}

myHarness.addFunctionMock('helper2')
helper2= function(){
    var returnValue =  myHarness.callFunctionSpy('helper2',arguments,function(codeToEval){eval(codeToEval)})
    if (returnValue!='NOVALUERETURNED')return eval(returnValue)
}

expect(VariableLiteral.getVariableLiteral(testFunction(A)).getLiteralAndCyclicDefinition('result')).equals('result = 76')
//here the program is called and the result is asserted

Known Limitations

When we set out to do this we thought the process would be completely automated: add one line of code, get a harness. Unfortunately it doesn't work like this. As we plowed our way through javascript we found more and more cases we should handle. For instance there is the not-so-complicated case where a global function returns a function. it sounded reasonable we should spy on that function and we nearly made it but it got complicated and even more complicated so we said no. And there other probably more cases we didn't think about. So, the point is you will get a harness, once you decide where is your starting point and where is the end point. The nice thing is that you get code. Once you have the code, the harness, you can start tweaking it! This tool will give you a good starting point and you can take it from there.

Contact me for any querie or comment: [email protected] OR [email protected]

copyright notice

Copyright (C) 2018 Yaki Koren

Redistribution, modification and use of this source code is allowed. You do need to mention the copyright. This software is intended to be used in a test environment only.