UNPKG

4.83 kBMarkdownView Raw
1# Deprivation
2
3This module facilitates *whitebox* and *blackbox* testing (binding it with conventional UT and MT paradigms) of *nodejs* applications.
4
5 > We define a module as a folder with implementations.
6
7- *whitebox unit*
8 - 'grants' a full access to an object, without the need to export everything in order to test it.
9 useful e.g. in *TDD* (test small increments without exposing every method), and writing fine-grained tests.
10 - can automatically mock other implementations
11 - useless (?) in module tests
12 - probably makes more problems in mature projects
13- *blackbox module*
14 - gives a *normal* access to an object
15 - can automatically mock other implementations
16
17 > Behind the curtains it uses the *Node*'s *VM* module, proxyquire, and plows the *require.cache*.
18
19## Usage
20
21```bash
22npm install deprivation
23cd node_modules/deprivation
24npm test
25```
26
27
28Example implementation (*Unit Under Test*).
29
30```javascript
31 var glob = require('glob');
32 var dep = require('./dep.js');
33
34 var myPrivateFunc = function(param){
35 return glob.GlobSync(param);
36 };
37
38 module.exports.publicFunc = function(param) {
39 return myPrivateFunc(param);
40 };
41
42 var callAnotherGlob = function() {
43 return dep('huhu');
44 };
45```
46
47### Basic
48
49An example test file.
50
51```javascript
52 var chamber = require("deprivation").chamber;
53 var session = chamber("./implementation.js");
54
55 // uut - Unit Under Test
56 var uut = session.whitebox();
57
58 uut.publicFunc("blabla"); // nothing special. Will call private func, which calls the original glob.GlobSync.
59 uut.myPrivateFunc("blabla"); // However... note that this func is not exported, but still accessible in a test!
60 uut.glob.GlobSync("blabla") // or even this...
61```
62
63### Replace dependencies
64
65It's possible to inject any type of a test double: *mock*, *spy*, *stub*, *fake*, etc., into the *UUT*.
66
67
68Example dependency of *UUT*.
69```javascript
70 // dep.js
71 module.exports = require('glob').GlobSync;
72```
73
74
75
76#### Right after the module is 'loaded'
77
78 - the UUT code is 'loaded' (= all the *require* statements are executed in the *UUT*)
79 - the dependencies are replaced after exposition of the *UUT*
80 - replacement is not transitive!
81
82```javascript
83// let's get rid of glob.GlobSync dependency
84 uut.glob.GlobSync = function(){};
85
86// all calls execute the dummy function
87 uut.publicFunc('blabla');
88 uut.myPrivateFunc('blabla');
89 uut.glob.GlobSync('blabla');
90
91// ...but not this one!
92 uut.callAnotherGlob();
93
94```
95
96#### Through an option
97
98Leads to a different result:
99 - if the replacement is an object, the require initialization code of the replaced dependencies is not executed
100 - if the replacement is a string (as in the require statement), the require initialization code **is** executed
101 - replacement is transitive (it is replaced globally)
102
103```javascript
104 var myGlob = {GlobSync: function() {return './.ssh/id_rsa.priv'}}
105 var session = chamber('./implementation.js', {replace:[{'glob': myGlob}]});
106 var uut = session.whitebox();
107
108 // all calls return './.ssh/id_rsa.priv'
109 uut.glob.GlobSync('something');
110 uut.callAnotherGlob('something');
111```
112#### Through an option, with more automation
113
114If a function exists, which accepts an object, and returns it's *test double*,
115
116```javascript
117// A jasmine spy-maker example
118
119 var myReplacer = function (obj) {
120 Object.keys(obj).forEach(function (item) {
121 spyOn(obj, item);
122 });
123 };
124```
125it can be passed on with the *replacer* option.
126
127```javascript
128 seance = chamber("myModule/impl.js", {replace: ['glob', '../*'], replacer: myReplacer});
129```
130
131In the above example
132 - the magical '../\*' string means that all implementations outside of *myModule* folder will be automatically transformed into spies. This omits the *node_module* folder.
133 - due to the above, the *glob* package is added explicitly, and will be automatically turned into a spy,
134
135An example test suite (jasmine/mocha):
136
137```javascript
138 beforeEach(function () {
139 sut = seance.blackbox();
140 spies = seance.getTestDoubles();
141 });
142```
143*spies* above are the spy objects references, stored in a dictionary. This allows to work with objects, that are inaccessible from the module's public interface.
144
145The expectation may be set, using the obtained references.
146
147```javascript
148 it('uses GlobSync', function () {
149 sut.arrangeHeapDumps('bleble');
150 expect(spies['node_modules/glob/glob.js'].GlobSync).toHaveBeenCalled();
151 });
152```
153
154Test doubles are accessed using the path relative to the process current directory. This is the most readable way to specify, which test double object is referenced (the *glob* package may be used by other sub-packages, in different versions, etc.)
155
156 > Refer to the test/\*.\* files for more examples.