[英]Creating immutable class for Node CommonJS module
我正在Node項目中創建一個基本類,我想使用Jest對其進行測試。 我收到一個錯誤,暗示要在測試中使用“嚴格”模式,我想避免/修復該錯誤
let LogRecord = require('../lib/logRecord');
describe('Test LogRecord functionality', () => {
test('LogRecord constructor', () => {
let timestamp = Date.now();
let logRecord = new LogRecord('INFO', 'Test Message', timestamp);
expect(logRecord.level).toBe('INFO');
expect(logRecord.message).toBe('Test Message');
expect(logRecord.timestamp).toBe(timestamp);
});
test('LogRecord is read-only', () => {
let timestamp = Date.now();
let logRecord = new LogRecord('INFO', 'Test Message', timestamp);
logRecord.level = 'WARN'
logRecord.message = 'New Message'
logRecord.timestamp = Date.now();
expect(logRecord.level).toBe('INFO');
expect(logRecord.message).toBe('Test Message');
expect(logRecord.timestamp).toBe(timestamp);
});
});
我正在對此進行測試:
Test LogRecord functionality › LogRecord constructor
TypeError: Cannot set property level of #<LogRecord> which has only a getter
1 | module.exports = class LogRecord {
2 | constructor(level, message, timestamp) {
> 3 | this.level = level;
| ^
4 | this.message = message;
5 | this.timestamp = timestamp ? timestamp : Date.now();
6 | }
at new LogRecord (lib/logRecord.js:3:9)
at Object.test (test/logRecord.test.js:6:25)
當我運行npm test
,兩個LogRecord測試都出現以下錯誤:
const data = new WeakMap();
let levelKey = {id:'level'};
let messageKey = {id:'message'};
let timestampKey = {id:'timestamp'};
module.exports = class LogRecord {
constructor(level, message, timestamp) {
data.set(levelKey, level);
data.set(messageKey, message);
data.set(timestampKey, timestamp ? timestamp : Date.now());
}
get level () {
return data.get(levelKey)
}
get message () {
return data.get(messageKey)
}
get timestamp () {
return data.get(timestampKey)
}
}
const data = new WeakMap(); let levelKey = {id:'level'}; let messageKey = {id:'message'}; let timestampKey = {id:'timestamp'}; module.exports = class LogRecord { constructor(level, message, timestamp) { data.set(levelKey, level); data.set(messageKey, message); data.set(timestampKey, timestamp ? timestamp : Date.now()); } get level () { return data.get(levelKey) } get message () { return data.get(messageKey) } get timestamp () { return data.get(timestampKey) } }
測試是要確保您的代碼能夠實現您認為的功能。 考慮以下類別:
class Foo {
constructor (bar) {
this._bar = bar;
}
get bar () {
return this._bar;
}
}
這里的bar
是只讀的 ,無法設置bar
屬性:
let foo = new Foo('a foo');
foo.bar; // 'a foo'
foo.bar = 'am not'; // TypeError!
模塊問題並不是真正相關的:因為注釋類主體中鏈接的Logar 始終是嚴格模式,無論如何。
因此,如果您希望屬性是只讀的,則不必擔心編寫它。 工作流程可能如下所示:
class Foo {}
並構造一個實例foo = new Foo()
如果您不希望使用只讀屬性,則可以添加一個setter:
class Foo {
constructor (bar) {
this._bar = bar;
}
get bar () {
return this._bar;
}
set bar (value) {
this._bar = value;
}
}
在這種情況下,您將添加一個測試,該測試會設置bar
並讀取更改后的值。
*您可能想知道為什么規范會保證這種行為時為什么要進行此測試,並且我認為該測試是必需的,因為有人(對於調用者而言)可以將該類重構為老式構造函數並創建一個向量錯誤:
// post refactor Foo
const Foo = function Foo(bar) {
this.bar = bar; // danger! now writable!
};
希望有經驗的審閱者會發現這種情況,但是我還是會編寫測試。
如果您想要的是在構造函數中設置的有保證的只讀屬性,則可以使用以下方法:
const data = new WeakMap();
module.exports = class Foo () {
constructor (bar) {
data.set(this, bar);
}
get bar () {
return data.get(this);
}
};
因為沒有導出data
,所以沒有外部代碼可以更改它。 嘗試設置實例的bar
屬性將引發異常。 只是使用getter和setter定義下划線屬性,這有點復雜,但是如果您要使用它,那么……我知道這種模式,因為我已經使用過它。
您只能為每個模塊 (而不是每個類或實例)創建一個weakmap。 弱映射存儲一個唯一的數據條目,該條目鍵入到各個實例(即this
):
const data = new WeakMap();
module.exports = {
Foo: class Foo () {
constructor (bar) {
data.set(this, bar);
}
get bar () {
return data.get(this);
}
},
Bar: class Bar () {
constructor (prop1, prop2) {
// for multiple props we'll store an object...
data.set(this, { prop2, prop1 });
}
get prop1 () {
// ...and retrieve it and access it's props to return
return data.get(this).prop1;
}
get prop2 () {
return data.get(this).prop2;
}
}
};
請注意,用吸氣劑設置道具,但不會有設置器會拋出...
// in someotherfile.js
const { Foo } = require('path/to/file/with/foo.js');
const foo = new Foo('imma foo');
foo.bar; // 'imma foo'
foo.bar = 'no not really'; // TypeError!
// you can still set random properties that don't have a getter:
foo.baz = 'I do not throw';
foo.baz; // 'I do not throw'
如果希望僅在對象初始化之后讀取屬性,則可以在構造函數中使用Object.freeze ,並刪除您的getter:
class LogRecord {
constructor(level, message, timestamp) {
this.level = level;
this.message = message;
this.timestamp = timestamp ? timestamp : Date.now();
Object.freeze(this);
}
}
但這將凍結對象的所有屬性。 之后,您將無法修改,刪除或添加任何內容。 沒有深入研究這一點,因此它也可能存在一些缺陷
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.