1 | # Deprivation
|
2 |
|
3 | This 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
|
22 | npm install deprivation
|
23 | cd node_modules/deprivation
|
24 | npm test
|
25 | ```
|
26 |
|
27 |
|
28 | Example 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 |
|
49 | An 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 |
|
65 | It's possible to inject any type of a test double: *mock*, *spy*, *stub*, *fake*, etc., into the *UUT*.
|
66 |
|
67 |
|
68 | Example 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 |
|
98 | Leads 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 |
|
114 | If 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 | ```
|
125 | it can be passed on with the *replacer* option.
|
126 |
|
127 | ```javascript
|
128 | seance = chamber("myModule/impl.js", {replace: ['glob', '../*'], replacer: myReplacer});
|
129 | ```
|
130 |
|
131 | In 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 |
|
135 | An 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 |
|
145 | The 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 |
|
154 | Test 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.
|