13 rewire - ranhs/soda-test GitHub Wiki
When you test your code, in manycases you need to access or change field in a modle that are not exported. Rewire gives a way to do this. For example when you define a spy or stub on a method in you own libraray that is not exported, soda-test uses rewire to spy/stub this method even if it is not exported.
For example in demo.ts there are those functions:
function callDB(filename: string): Promise<string> {...}
export async function bar(filename: string): Promise<string> {
await createFile(filename)
const result = await callDB(filename)
return result
}
If you want to test the bar method, but you don't want it to call the the 'real' callDB method, you need to stub the callDB private method. You can define a stub to callDB. Note that you cannot import callDB from demo.ts:
import { callDB } from './demo'
You also cannot replace the callDB method with a stub this way:
import * as demo from './demo'
demo.callDB = callDBstub
The above will not work since callDB is not exported and not part of demo model public members. The stub will not be called when bar method is called. (by the way, even if callDB method was exported, this would not worked, due to the way tsc translate export exptression to JS).
To create this stub you need to use rewire. Soda-test uses rewire under the hood for spies and stubs when you do the folowing:
@stub('./demo', 'callDB').resolves('calldb_stub')
callDbStub: SinonStub
In order for rewire to work (even when it is used internaly) you must load the soda-test model before loading any model that might need to be rewired. You must make the import statement for soda-test the first line in each test model that also imports any modle that might need to be rewire, even if it is rewired (tested) in a different test model.
This line is always the first one:
import { describe, context, it, TR, ... } from 'soda-test'
You can use rewire yourself. define a varible/argument of type Rewire as member variable in the global context or in a specific context, or as an argument to the test-step method. set a decorator on that varible/argument named rewire with a single argument: the relative location of the libraray to rewire as state in the import statement:
import {describe, context, it, TR, rewire, Rewire} from 'soda-test'
import {getCount} from './lib'
@describe("TestTest")
class TestTest {
@it("should rewire lib as argument")
checkRewire1(@rewire('./lib') libRewire: Rewire): TR {...}
@rewire('./lib2')
lib2Rewire: Rewire
@it("should rewire lib2 as member")
checkRewire2(): TR {...}
}
to access a field in the rewired model (whether public or private) use the get method on the Rewire varible with the name of the field to get as string argument. the get method returns the value of the this field at this point:
const name = this.libRewire.get('_count')
to change the value of a filed in the rewired model (whether public or private) use the set method on the Rewire vairble with 2 arguments:
- the name of the field to set as string argument
- the new value to set this filed to
libRewire.set('_count', 222)
Note: any value that was changed using the set method will be restored to its original after each test-step, or when calling the reset method on the Rewire variable
The following is part of the code in lib.ts file:let _count: number
export function getCount(): number {
return _count
}
export function setCount(value: number): void {
_count = value
}
The following is part of the code in lib.test.ts:
import {describe, context, it, TR, expect, rewire, Rewire} from 'soda-test'
import {getCount, setCount} from './lib'
@describe("TestTest")
class TestTest {
beforeEach(): void {
setCount(18)
}
@context("rewire")
@it("should rewire lib as argument")
checkRewire1(@rewire('./lib') libRewire: Rewire): TR {
const count = libRewire.get('_count')
expect(count).to.equal(18)
libRewire.set('_count', 222)
expect(getCount()).to.equal(222)
libRewire.set('getCount', ()=>libRewire.get('_count')+1)
expect(getCount()).to.equal(223)
}
@it("should reset rewire changes")
checkRewire1After(): TR {
expect(getCount()).to.equal(18)
}
The code in checkRewire1 reads the value of the private varible _count and validate it is 18 (since it was set to 18 using the setCount public method in the beforeEach control method).
Next the code sets the private _count varible to the value of 222. It calls the public method getCount and validates the value indeed was changed to 222.
Next the test-code sets the getCount itself to a different method (note that this can also be done using a stub) the new method returns the value of the private method _count after added 1 to it. The test code validate that getCount method (that was replaced) returns 223 alghough the private _count varible is still 222.
In the next checkRewire1After test step, the implementation of getCount was restored to its original, and also the value of _count (and it also set to 18 by the beforeEach method). The test validates that getCount now returns the 'real' value of _count the value is 18.
By default when using rewire you are getting a Rewire object to the loaded libraray. In some cases you what to test the loading of the libraray itself. You can pass a second argument to the rewire decorator. This argument is boolean value, false by default. If this value is ture, the libraray is loaded again. All the method/varibles of this libraray will be those of the reloaded version.
let value1 = Math.random()
export const value = value1
import { expect, describe, context, it, TR, Rewire, rewire } from 'soda-test'
import { value } from './reloadLib'
@describe('rewire-reload')
class RewireReloadTest {
@context('0')
orgValue: number
@it('should be a random value (original)')
check0(): TR {
this.orgValue = value
expect(this.orgValue).to.be.a('number')
}
@context('1')
tempValue: number
@rewire('./reloadLib', true)
reloadLib1: Rewire
@it('should be a random value (changed)')
check1(): TR {
this.tempValue = value
expect(this.tempValue).to.equal(this.reloadLib1.get('value1'))
expect(this.tempValue).to.not.equal(this.orgValue)
}
@context('2')
@it('should be the original value')
check2(): TR {
expect(value).to.equal(this.orgValue)
}
}
Note that in context 1 we defined a rewire for the reloadLib libraray with the reload flag set to true. There for the value1 variable was set with a new random number that is not the one saved in context 0. However in context 2 that rewire is not valid anymore, and the value of value1 varaible (and therefor the value varible) is as it was in context 0.