// Copyright 2008 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. goog.provide('goog.testing.LooseMockTest'); goog.setTestOnly('goog.testing.LooseMockTest'); goog.require('goog.testing.LooseMock'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.mockmatchers'); // The object that we will be mocking var RealObject = function() {}; RealObject.prototype.a = function() { fail('real object should never be called'); }; RealObject.prototype.b = function() { fail('real object should never be called'); }; var mock; var stubs; function setUp() { var obj = new RealObject(); mock = new goog.testing.LooseMock(obj); stubs = new goog.testing.PropertyReplacer(); } function tearDown() { stubs.reset(); } /* * Calling this method evades the logTestFailure in * goog.testing.Mock.prototype.$recordAndThrow, so it doesn't look like the * test failed. */ function silenceFailureLogging() { if (goog.global['G_testRunner']) { stubs.set(goog.global['G_testRunner'], 'logTestFailure', goog.nullFunction); } } function unsilenceFailureLogging() { stubs.reset(); } /** * Version of assertThrows that doesn't log the exception generated by the * mocks under test. * TODO: would be nice to check that a particular substring is in the thrown * message so we know it's not something dumb like a syntax error */ function assertThrowsQuiet(var_args) { silenceFailureLogging(); assertThrowsJsUnitException.apply(null, arguments); unsilenceFailureLogging(); } // Most of the basic functionality is tested in strictmock_test; these tests // cover the cases where loose mocks are different from strict mocks function testSimpleExpectations() { mock.a(5); mock.b(); mock.$replay(); mock.a(5); mock.b(); mock.$verify(); mock.$reset(); mock.a(); mock.b(); mock.$replay(); mock.b(); mock.a(); mock.$verify(); mock.$reset(); mock.a(5).$times(2); mock.a(5); mock.a(2); mock.$replay(); mock.a(5); mock.a(5); mock.a(5); mock.a(2); mock.$verify(); } function testMultipleExpectations() { mock.a().$returns(1); mock.a().$returns(2); mock.$replay(); assertEquals(1, mock.a()); assertEquals(2, mock.a()); mock.$verify(); } function testMultipleExpectationArgs() { mock.a('asdf').$anyTimes(); mock.a('qwer').$anyTimes(); mock.b().$times(3); mock.$replay(); mock.a('asdf'); mock.b(); mock.a('asdf'); mock.a('qwer'); mock.b(); mock.a('qwer'); mock.b(); mock.$verify(); mock.$reset(); mock.a('asdf').$anyTimes(); mock.a('qwer').$anyTimes(); mock.$replay(); mock.a('asdf'); mock.a('qwer'); goog.bind(mock.a, mock, 'asdf'); goog.bind(mock.$verify, mock); } function testSameMethodOutOfOrder() { mock.a('foo').$returns(1); mock.a('bar').$returns(2); mock.$replay(); assertEquals(2, mock.a('bar')); assertEquals(1, mock.a('foo')); } function testSameMethodDifferentReturnValues() { mock.a('foo').$returns(1).$times(2); mock.a('foo').$returns(3); mock.a('bar').$returns(2); mock.$replay(); assertEquals(1, mock.a('foo')); assertEquals(2, mock.a('bar')); assertEquals(1, mock.a('foo')); assertEquals(3, mock.a('foo')); assertThrowsQuiet(function() { mock.a('foo'); mock.$verify(); }); } function testSameMethodBrokenExpectations() { // This is a weird corner case. // No way to ever make this verify no matter what you call after replaying, // because the second expectation of mock.a('foo') will be masked by // the first expectation that can be called any number of times, and so we // can never satisfy that second expectation. mock.a('foo').$returns(1).$anyTimes(); mock.a('bar').$returns(2); mock.a('foo').$returns(3); // LooseMock can detect this case and fail on $replay. assertThrowsQuiet(goog.bind(mock.$replay, mock)); mock.$reset(); // This is a variant of the corner case above, but it's harder to determine // that the expectation to mock.a('bar') can never be satisfied. So we don't // fail on $replay, but we do fail on $verify. mock.a(goog.testing.mockmatchers.isString).$returns(1).$anyTimes(); mock.a('bar').$returns(2); mock.$replay(); assertEquals(1, mock.a('foo')); assertEquals(1, mock.a('bar')); assertThrowsQuiet(goog.bind(mock.$verify, mock)); } function testSameMethodMultipleAnyTimes() { mock.a('foo').$returns(1).$anyTimes(); mock.a('foo').$returns(2).$anyTimes(); mock.$replay(); assertEquals(1, mock.a('foo')); assertEquals(1, mock.a('foo')); assertEquals(1, mock.a('foo')); // Note we'll never return 2 but that's ok. mock.$verify(); } function testFailingFast() { mock.a().$anyTimes(); mock.$replay(); mock.a(); mock.a(); assertThrowsQuiet(goog.bind(mock.b, mock)); mock.$reset(); // too many mock.a(); mock.b(); mock.$replay(); mock.a(); mock.b(); var message; silenceFailureLogging(); try { mock.a(); } catch (e) { message = e.message; } unsilenceFailureLogging(); assertTrue('No exception thrown on unexpected call', goog.isDef(message)); assertContains('Too many calls to a', message); } function testTimes() { mock.a().$times(3); mock.b().$times(2); mock.$replay(); mock.a(); mock.b(); mock.b(); mock.a(); mock.a(); mock.$verify(); } function testFailingSlow() { // not enough mock.a().$times(3); mock.$replay(); mock.a(); mock.a(); assertThrowsQuiet(goog.bind(mock.$verify, mock)); mock.$reset(); // not enough, interleaved order mock.a().$times(3); mock.b().$times(3); mock.$replay(); mock.a(); mock.b(); mock.a(); mock.b(); assertThrowsQuiet(goog.bind(mock.$verify, mock)); mock.$reset(); // bad args mock.a('asdf').$anyTimes(); mock.$replay(); mock.a('asdf'); assertThrowsQuiet(goog.bind(mock.a, mock, 'qwert')); assertThrowsQuiet(goog.bind(mock.$verify, mock)); } function testArgsAndReturns() { mock.a('asdf').$atLeastOnce().$returns(5); mock.b('qwer').$times(2).$returns(3); mock.$replay(); assertEquals(5, mock.a('asdf')); assertEquals(3, mock.b('qwer')); assertEquals(5, mock.a('asdf')); assertEquals(5, mock.a('asdf')); assertEquals(3, mock.b('qwer')); mock.$verify(); } function testThrows() { mock.a().$throws('exception!'); mock.$replay(); assertThrows(goog.bind(mock.a, mock)); mock.$verify(); } function testDoes() { mock.a(1, 2).$does(function(a, b) { return a + b; }); mock.$replay(); assertEquals('Mock should call the function', 3, mock.a(1, 2)); mock.$verify(); } function testIgnoresExtraCalls() { mock = new goog.testing.LooseMock(RealObject, true); mock.a(); mock.$replay(); mock.a(); mock.b(); // doesn't throw mock.$verify(); } function testSkipAnyTimes() { mock = new goog.testing.LooseMock(RealObject); mock.a(1).$anyTimes(); mock.a(2).$anyTimes(); mock.a(3).$anyTimes(); mock.$replay(); mock.a(1); mock.a(3); mock.$verify(); } function testErrorMessageForBadArgs() { mock.a(); mock.$anyTimes(); mock.$replay(); var message; silenceFailureLogging(); try { mock.a('a'); } catch (e) { message = e.message; } unsilenceFailureLogging(); assertTrue('No exception thrown on verify', goog.isDef(message)); assertContains('Bad arguments to a()', message); }