Skip to content

Commit

Permalink
Rollforward of cl/211538380: Add a $waitAndVerify() function
Browse files Browse the repository at this point in the history
NEW: Assign goog.getUid to a local private const var in Set, preventing the global mocking behavior of some tests from impacting Set.

RELNOTES: Add a $waitAndVerify() function to MockInterface and a $waitAndVerifyAll() function to MockControl that returns a goog.Promise that resolves when expectations have been registered.

Automated g4 rollback of changelist 211629440.

*** Reason for rollback ***

Rollforward by not allowing goog.getUid to be mocked for Set.

*** Original change description ***

Automated g4 rollback of changelist 211538380.

*** Reason for rollback ***

Broken builds.

*** Original change description ***

Add a $waitAndVerify() function to MockInterface and a $waitAndVerifyAll() function to MockControl that returns a goog.Promise that resolves when expectations have been registered.

RELNOTES: same

***

***

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=211661074
  • Loading branch information
Nick Reid committed Sep 5, 2018
1 parent 6d3f93c commit 82d84ea
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 12 deletions.
16 changes: 8 additions & 8 deletions closure/goog/deps.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions closure/goog/structs/set.js
Expand Up @@ -30,8 +30,6 @@ goog.require('goog.structs');
goog.require('goog.structs.Collection');
goog.require('goog.structs.Map');



/**
* A set that can contain both primitives and objects. Adding and removing
* elements is O(1). Primitives are treated as identical if they have the same
Expand All @@ -56,6 +54,12 @@ goog.structs.Set = function(opt_values) {
}
};

/**
* A function that returns a unique id.
* @private @const {function(?Object): number}
*/
goog.structs.Set.getUid_ = goog.getUid;


/**
* Obtains a unique key for an element of the set. Primitives will yield the
Expand All @@ -68,7 +72,7 @@ goog.structs.Set = function(opt_values) {
goog.structs.Set.getKey_ = function(val) {
var type = typeof val;
if (type == 'object' && val || type == 'function') {
return 'o' + goog.getUid(/** @type {Object} */ (val));
return 'o' + goog.structs.Set.getUid_(/** @type {Object} */ (val));
} else {
return type.substr(0, 1) + val;
}
Expand Down
41 changes: 41 additions & 0 deletions closure/goog/testing/loosemock.js
Expand Up @@ -21,7 +21,9 @@ goog.provide('goog.testing.LooseExpectationCollection');
goog.provide('goog.testing.LooseMock');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.structs.Map');
goog.require('goog.structs.Set');
goog.require('goog.testing.Mock');


Expand Down Expand Up @@ -93,6 +95,9 @@ goog.testing.LooseMock = function(
*/
this.$expectations_ = new goog.structs.Map();

/** @private {!goog.structs.Set<!goog.testing.MockExpectation>} */
this.awaitingExpectations_ = new goog.structs.Set();

/**
* The calls that have been made; we cache them to verify at the end. Each
* element is an array where the first element is the name, and the second
Expand Down Expand Up @@ -134,6 +139,9 @@ goog.testing.LooseMock.prototype.$recordExpectation = function() {

var collection = this.$expectations_.get(this.$pendingExpectation.name);
collection.addExpectation(this.$pendingExpectation);
if (this.$pendingExpectation) {
this.awaitingExpectations_.add(this.$pendingExpectation);
}
};


Expand Down Expand Up @@ -172,6 +180,10 @@ goog.testing.LooseMock.prototype.$recordCall = function(name, args) {
matchingExpectation.maxCalls + ' but was: ' +
matchingExpectation.actualCalls);
}
if (matchingExpectation.actualCalls >= matchingExpectation.minCalls) {
this.awaitingExpectations_.remove(matchingExpectation);
this.maybeFinishedWithExpectations_();
}

this.$calls_.push([name, args]);
return this.$do(matchingExpectation, args);
Expand All @@ -183,6 +195,7 @@ goog.testing.LooseMock.prototype.$reset = function() {
goog.testing.LooseMock.superClass_.$reset.call(this);

this.$expectations_ = new goog.structs.Map();
this.awaitingExpectations_ = new goog.structs.Set();
this.$calls_ = [];
};

Expand Down Expand Up @@ -223,6 +236,34 @@ goog.testing.LooseMock.prototype.$replay = function() {
};


/** @override */
goog.testing.LooseMock.prototype.$waitAndVerify = function() {
var keys = this.$expectations_.getKeys();
for (var i = 0; i < keys.length; i++) {
var expectations = this.$expectations_.get(keys[i]).getExpectations();
for (var j = 0; j < expectations.length; j++) {
var expectation = expectations[j];
goog.asserts.assert(
!isFinite(expectation.maxCalls) ||
expectation.minCalls == expectation.maxCalls,
'Mock expectations cannot have a loose number of expected calls to ' +
'use $waitAndVerify.');
}
}
var promise = goog.testing.LooseMock.base(this, '$waitAndVerify');
this.maybeFinishedWithExpectations_();
return promise;
};

/**
* @private
*/
goog.testing.LooseMock.prototype.maybeFinishedWithExpectations_ = function() {
if (this.awaitingExpectations_.isEmpty() && this.waitingForExpectations) {
this.waitingForExpectations.resolve();
}
};

/** @override */
goog.testing.LooseMock.prototype.$verify = function() {
goog.testing.LooseMock.superClass_.$verify.call(this);
Expand Down
48 changes: 48 additions & 0 deletions closure/goog/testing/loosemock_test.js
Expand Up @@ -290,3 +290,51 @@ function testErrorMessageForBadArgs() {

assertContains('Bad arguments to a()', e.message);
}

async function testWaitAndVerify() {
mock.a();
mock.$replay();

setTimeout(() => {
mock.a();
}, 0);
await mock.$waitAndVerify();
}

async function testWaitAndVerify_Synchronous() {
mock.a();
mock.$replay();

mock.a();
await mock.$waitAndVerify();
}

async function testWaitAndVerify_Exception() {
mock.a();
mock.$replay();

setTimeout(() => {
assertThrowsJsUnitException(() => {
mock.a(false);
});
}, 0);
await assertRejects(mock.$waitAndVerify());
}

async function testWaitAndVerify_Reset() {
mock.a();
mock.$replay();

setTimeout(() => {
mock.a();
}, 0);
await mock.$waitAndVerify();
mock.$reset();
mock.a();
mock.$replay();

setTimeout(() => {
mock.a();
}, 0);
await mock.$waitAndVerify();
}
34 changes: 34 additions & 0 deletions closure/goog/testing/mock.js
Expand Up @@ -39,8 +39,11 @@ goog.setTestOnly('goog.testing.Mock');
goog.provide('goog.testing.Mock');
goog.provide('goog.testing.MockExpectation');

goog.require('goog.Promise');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.object');
goog.require('goog.promise.Resolver');
goog.require('goog.testing.JsUnitException');
goog.require('goog.testing.MockInterface');
goog.require('goog.testing.mockmatchers');
Expand Down Expand Up @@ -193,6 +196,9 @@ goog.testing.Mock = function(
this.$initializeFunctions_(objectToMock);
}
this.$argumentListVerifiers_ = {};

/** @protected {?goog.promise.Resolver<undefined>} */
this.waitingForExpectations = null;
};


Expand Down Expand Up @@ -543,6 +549,9 @@ goog.testing.Mock.prototype.$reset = function() {
this.$recording_ = true;
this.$threwException_ = null;
delete this.$pendingExpectation;
if (this.waitingForExpectations) {
this.waitingForExpectations = null;
}
};


Expand All @@ -569,6 +578,9 @@ goog.testing.Mock.prototype.$throwException = function(comment, opt_message) {
* @protected
*/
goog.testing.Mock.prototype.$recordAndThrow = function(ex, rethrow) {
if (this.waitingForExpectations) {
this.waitingForExpectations.resolve();
}
// If it's an assert exception, record it.
if (ex['isJsUnitException']) {
if (!this.$threwException_) {
Expand All @@ -590,6 +602,28 @@ goog.testing.Mock.prototype.$recordAndThrow = function(ex, rethrow) {
};


/** @override */
goog.testing.Mock.prototype.$waitAndVerify = function() {
goog.asserts.assert(
!this.$recording_,
'$waitAndVerify should be called after recording calls.');
this.waitingForExpectations = goog.Promise.withResolver();
var verify = goog.bind(this.$verify, this);
return this.waitingForExpectations.promise.then(function() {
return new goog.Promise(function(resolve, reject) {
setTimeout(function() {
try {
verify();
} catch (e) {
reject(e);
}
resolve();
}, 0);
});
});
};


/**
* Verify that all of the expectations were met. Should be overridden by
* subclasses.
Expand Down
20 changes: 19 additions & 1 deletion closure/goog/testing/mock_test.js
Expand Up @@ -345,4 +345,22 @@ function testMockEs6ClassStaticMethods() {
assertEquals('a', mock.a());
assertEquals('apply', mock.apply());
mockControl.$verifyAll();
}
}

async function testLooseMockAsynchronousVerify() {
const mockControl = new goog.testing.MockControl();
const looseMock = mockControl.createLooseMock(RealObject);
looseMock.a().$returns('a');

const strictMock = mockControl.createStrictMock(RealObject);
strictMock.a().$returns('a');

mockControl.$replayAll();
setTimeout(() => {
looseMock.a();
}, 0);
setTimeout(() => {
strictMock.a();
}, 0);
await mockControl.$waitAndVerifyAll();
}
13 changes: 13 additions & 0 deletions closure/goog/testing/mockcontrol.js
Expand Up @@ -30,6 +30,7 @@
goog.setTestOnly('goog.testing.MockControl');
goog.provide('goog.testing.MockControl');

goog.require('goog.Promise');
goog.require('goog.array');
goog.require('goog.testing');
goog.require('goog.testing.LooseMock');
Expand Down Expand Up @@ -80,6 +81,18 @@ goog.testing.MockControl.prototype.$resetAll = function() {
};


/**
* Returns a Promise that resolves when all of the controlled mocks have
* finished and verified.
* @return {!goog.Promise<!Array<undefined>>}
*/
goog.testing.MockControl.prototype.$waitAndVerifyAll = function() {
return goog.Promise.all(goog.array.map(this.mocks_, function(m) {
return m.$waitAndVerify();
}));
};


/**
* Calls verify on each controlled mock.
*/
Expand Down
9 changes: 9 additions & 0 deletions closure/goog/testing/mockinterface.js
Expand Up @@ -20,6 +20,8 @@
goog.setTestOnly('goog.testing.MockInterface');
goog.provide('goog.testing.MockInterface');

goog.require('goog.Promise');



/** @interface */
Expand All @@ -40,6 +42,13 @@ goog.testing.MockInterface.prototype.$replay = function() {};
goog.testing.MockInterface.prototype.$reset = function() {};


/**
* Waits for the Mock to gather expectations and then performs verify.
* @return {!goog.Promise<undefined>}
*/
goog.testing.MockInterface.prototype.$waitAndVerify = function() {};


/**
* Assert that the expected function calls match the actual calls.
*/
Expand Down

0 comments on commit 82d84ea

Please sign in to comment.