在JavaScript中是否可能模拟document.cookie?

29

document.cookie 就像一个字符串,但它并不是一个字符串。举个例子,引用自Mozilla doc:

document.cookie = "name=oeschger";
document.cookie = "favorite_food=tripe";
alert(document.cookie);
// displays: name=oeschger;favorite_food=tripe
如果您尝试仅使用字符串创建模拟 cookie,则 不会 获得相同的结果:
var mockCookie = "";
mockCookie = "name=oeschger";
mockCookie = "favorite_food=tripe";
alert(mockCookie);
// displays: favorite_food=tripe

如果你想对操作cookie的模块进行单元测试,并且想在这些测试中使用模拟cookie,那么你可以吗?如何做?


较新的Web浏览器中的getter和setter。 - zzzzBov
你可以尝试我为此目的编写的一个简单的模拟对象:https://github.com/RichardKnop/CookieMock - Richard Knop
7个回答

22
您可以创建一个具有cookie设置器和getter的对象。这是一个非常简单的实现:
var mock = {
    value_: '', 

    get cookie() {
        return this.value_;
    },

    set cookie(value) {
        this.value_ += value + ';';
    }
};

在某些浏览器中可能无法正常工作(特别是IE)。更新:它仅适用于支持ECMAScript 5的浏览器!

了解更多有关getter和setter的信息

mock.cookie = "name=oeschger";
mock.cookie = "favorite_food=tripe";
alert(mock.cookie);
// displays: name=oeschger;favorite_food=tripe;

演示


那么,你的意思是你当然可以模仿行为,但接口不能相同。对吗? - thisgeek
@thisgeek:不太确定你的意思...它是相同的接口还是我漏掉了什么? - Felix Kling
我误读了你写的内容。在 getcookie 之间没有 :,但是我的大脑仍然把它放在那里了。这意味着你的解决方案依赖于 JS 引擎中的 ECMAScript 5 支持,我可以接受。 - thisgeek
+1 但请注意,“get/set”(以及__defineGetter__/__defineSetter__)构造是由ECMA-262第5版(ECMAScript 5)指定的,正如您所提到的,它受MSIE支持,但大多数其他浏览器都支持。 - maerics

9

此实现允许覆盖Cookies,并添加document.clearCookies()

(function (document) {
    var cookies = {};
    document.__defineGetter__('cookie', function () {
        var output = [];
        for (var cookieName in cookies) {
            output.push(cookieName + "=" + cookies[cookieName]);
        }
        return output.join(";");
    });
    document.__defineSetter__('cookie', function (s) {
        var indexOfSeparator = s.indexOf("=");
        var key = s.substr(0, indexOfSeparator);
        var value = s.substring(indexOfSeparator + 1);
        cookies[key] = value;
        return key + "=" + value;
    });
    document.clearCookies = function () {
        cookies = {};
    };
})(document);

应该使用";"分隔符连接,以遵循实际实现:return output.join("; "); - Alvis

8

@Felix Kling的回答是正确的,我只想指出在ECMAScript 5中定义setter和getter的另一种语法:

function MockCookie() {
  this.str = '';
  this.__defineGetter__('cookie', function() {
    return this.str;
  });
  this.__defineSetter__('cookie', function(s) {
    this.str += (this.str ? ';' : '') + s;
    return this.str;
  });
}
var mock = new MockCookie();
mock.cookie = 'name=oeschger';
mock.cookie = 'favorite_food=tripe';
mock.cookie; // => "name=oeschger;favorite_food=tripe"

再次提醒,大多数浏览器支持 ECMAScript 5(由 ECMA-262 第五版定义),但不支持 MSIE(或 JScript)。


当更新 cookie 值时(会出现重复键)会导致此操作失败。 - Dzhuang

7
我发现Jasmine有一个 spyOnProperty 方法,可以用于在对象的getter和setter上进行监视。所以我用以下代码解决了我的问题:
const cookie: string = 'my-cookie=cookievalue;';    
spyOnProperty(document, 'cookie', 'get').and.returnValue(cookie);

6

以下是我在 Jest 中的处理方式:

在添加了 Secure 属性后,我的 Cookie 不再被添加到 document.cookie 中(因为 document.location 是不安全的 http://localhost)。

经过多次尝试和错误的尝试(尝试拦截 document.cookie 的设置器并从中删除其 ;Secure,但调用新值的 document.cookie = 触发无限循环,因为它重新进入设定器...),我最终采用了这个非常简单的解决方案:

beforeEach(function() {
  let cookieJar = document.cookie;
  jest.spyOn(document, 'cookie', 'set').mockImplementation(cookie => {
    cookieJar += cookie;
  });
  jest.spyOn(document, 'cookie', 'get').mockImplementation(() => cookieJar);
})

2

我知道这是一个老话题,但在我的情况下,过期的 cookies 是必要的,所以这里提供了一个解决方案,它将上述答案与 setTimeout 调用相结合,在 X 秒后使 cookies 过期:

const fakeCookies = {
    // cookie jar
    all: {},

    // timeouts
    timeout: {},

    // get a cookie
    get: function(name)
    {
        return this.all[ name ]
    },

    // set a cookie
    set: function(name, value, expires_seconds)
    {
        this.all[ name ] = value;

        if ( expires_seconds ) {
            ! this.timeout[ name ] || clearTimeout( this.timeout[ name ] )
            this.timeout[ name ] = setTimeout(() => this.unset(name), parseFloat(expires_seconds) * 1000)
        }
    },

    // delete a cookie
    unset: function(name)
    {
        delete this.all[ name ]
    }    
}

1

就我个人而言,我无法劫持文档对象。 一个对我似乎有效的简单解决方案是以下内容...

在我的测试脚本顶部,我定义了一个fakeCookie对象:

var fakeCookie = {
    cookies: [],
    set: function (k, v) {
        this.cookies[k] = v;
    },
    get: function (k) {
        return this.cookies[k];
    },
    reset: function () {
        this.cookies = [];
    }
};

在我的beforeEach()函数中,我定义了一个cookie存根。这基本上拦截对jQuery.cookie的调用,并调用我定义的回调函数(见下文)代替它。
beforeEach(function() {
    var cookieStub = sinon.stub(jQuery, "cookie", function() {
        if (arguments.length > 1) {
            fakeCookie.set(arguments[0], arguments[1]);
        }
        else {
            return fakeCookie.get(arguments[0]);
        }
    });
});

每当我获取或设置cookie值时,它都使用我的fakeCookie而不是真正的jQuery.cookie。它通过查看传递的参数数量并推断出它是get/set来实现这一点。我只需粘贴它,就可以直接运行了。 希望这有所帮助!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接