Henning Snater has uploaded a new change for review.
https://gerrit.wikimedia.org/r/177503
Change subject: Rewrote RepoApi tests
......................................................................
Rewrote RepoApi tests
Implemented mocking/spying using SinonJS. No actual API request are triggered
anymore.
Change-Id: I3d1d3af84cf8924115ef38ef319ae09b51bd0ddf
---
M .jshintrc
M tests/RepoApi.tests.js
2 files changed, 218 insertions(+), 495 deletions(-)
git pull
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikibaseJavaScriptApi
refs/changes/03/177503/1
diff --git a/.jshintrc b/.jshintrc
index 832d316..09f9407 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -57,6 +57,7 @@
"jQuery",
"mediaWiki",
"QUnit",
+ "sinon",
"util",
"wikibase"
]
diff --git a/tests/RepoApi.tests.js b/tests/RepoApi.tests.js
index e59aa83..cde2b2f 100644
--- a/tests/RepoApi.tests.js
+++ b/tests/RepoApi.tests.js
@@ -1,520 +1,242 @@
/**
- * QUnit tests for wikibase.api.RepoApi
- * @see https://www.mediawiki.org/wiki/Extension:Wikibase
- *
* @licence GNU GPL v2+
* @author H. Snater < [email protected] >
*/
-
-( function( mw, wb, $, QUnit, undefined ) {
+( function( mw, wb, QUnit, sinon ) {
'use strict';
- var mwApi = new mw.Api();
+QUnit.module( 'wikibase.api.RepoApi' );
- /**
- * wikibase.api.RepoApi object
- * @var {Object}
- */
- var api = new wb.api.RepoApi( mwApi );
+/**
+ * Instantiates a `wikibase.api.RepoApi` object with the relevant method being
overwritten and
+ * having applied a SinonJS spy.
+ *
+ * @param {string} [getOrPost='post'] Whether to mock/spy the `get` or `post`
request.
+ * @return {Object}
+ */
+function mockApi( getOrPost ) {
+ var spyPost = getOrPost !== 'get',
+ api = new mw.Api();
- /**
- * Queue used run asynchronous tests synchronously.
- * @var {jQuery}
- */
- var testrun = $( {} );
+ api.postWithToken = function() {};
+ api.get= function() {};
- /**
- * Queue key naming the queue that all tests are appended to.
- * @var {String}
- */
- var qkey = 'asyncTests';
-
- /**
- * Since jQuery.queue does not allow passing parameters, this variable
will cache the data
- * structures of entities.
- * @var {Object}
- */
- var entityStack = [];
-
- /**
- * Triggers running the tests attached to the test queue.
- */
- var runTest = function() {
- QUnit.stop();
- testrun.queue( qkey, function() {
- QUnit.start(); // finish test run
- } );
- testrun.dequeue( qkey ); // start tests
+ return {
+ spy: sinon.spy( api, spyPost ? 'postWithToken' : 'get' ),
+ api: new wb.api.RepoApi( api )
};
+}
- /**
- * Handles a failing API request. (The details get logged in the
console by mw.Api.)
- *
- * @param {String} code
- * @param {Object} details
- */
- var onFail = function( code, details ) {
- QUnit.assert.ok(
- false,
- 'API request failed returning code: "' + code + '". See
console for details.'
+/**
+ * Returns all request parameters submitted to the function performing the
`get` or `post` request.
+ *
+ * @param {Object} spy The SinonJS spy to extract the parameters from.
+ * @param [callIndex=0] The call index if multiple API calls have been
performed on the same spy.
+ * @return {Object}
+ */
+function getParams( spy, callIndex ) {
+ callIndex = callIndex || 0;
+ return spy.displayName === 'postWithToken' ? spy.args[callIndex][1] :
spy.args[callIndex][0];
+}
+
+/**
+ * Returns a specific parameter submitted to the function performing the `get`
or `post` request.
+ *
+ * @param {Object} spy The SinonJS spy to extract the parameters from.
+ * @param {string} paramName
+ * @param [callIndex=0] The call index if multiple API calls have been
performed on the same spy.
+ * @return {string}
+ */
+function getParam( spy, paramName, callIndex ) {
+ return getParams( spy, callIndex || 0 )[paramName];
+}
+
+QUnit.test( 'createEntity()', function( assert ) {
+ var mock = mockApi();
+
+ mock.api.createEntity( 'item' );
+ mock.api.createEntity( 'property', {
+ datatype: 'string'
+ } );
+
+ assert.ok( mock.spy.calledTwice, 'Triggered API calls.' );
+
+ assert.equal(
+ getParam( mock.spy, 'action' ),
+ 'wbeditentity',
+ 'Verified API module being called.'
+ );
+
+ assert.equal(
+ getParam( mock.spy, 'new' ),
+ 'item',
+ 'Verified submitting entity type.'
+ );
+
+ assert.equal(
+ getParam( mock.spy, 'data' ),
+ JSON.stringify( {} ),
+ 'Verified not submitting any data by default.'
+ );
+
+ assert.equal(
+ getParam( mock.spy, 'data', 1 ),
+ JSON.stringify( { datatype: 'string' } ),
+ 'Verified submitting "datatype" field.'
+ );
+} );
+
+QUnit.test( 'editEntity()', function( assert ) {
+ var mock = mockApi(),
+ data = {
+ labels: {
+ de: {
+ language: 'de',
+ value: 'label'
+ }
+ }
+ };
+
+ mock.api.editEntity( 'entity id', 12345, data );
+
+ assert.ok( mock.spy.calledOnce, 'Triggered API call.' );
+
+ assert.equal(
+ getParam( mock.spy, 'action' ),
+ 'wbeditentity',
+ 'Verified API module being called.'
+ );
+
+ assert.equal( getParam( mock.spy, 'id' ), 'entity id' );
+ assert.equal( getParam( mock.spy, 'baserevid' ), 12345 );
+ assert.equal( getParam( mock.spy, 'data' ), JSON.stringify( data ) );
+} );
+
+QUnit.test( 'searchEntities()', function( assert ) {
+ var mock = mockApi( 'get' );
+
+ mock.api.searchEntities( 'label', 'language code', 'entity type', 10, 5
);
+
+ assert.ok( mock.spy.calledOnce, 'Triggered API call.' );
+
+ assert.equal(
+ getParam( mock.spy, 'action' ),
+ 'wbsearchentities',
+ 'Verified API module being called.'
+ );
+
+ assert.equal( getParam( mock.spy, 'search' ), 'label' );
+ assert.equal( getParam( mock.spy, 'language' ), 'language code' );
+ assert.equal( getParam( mock.spy, 'type' ), 'entity type' );
+ assert.equal( getParam( mock.spy, 'limit' ), 10 );
+ assert.equal( getParam( mock.spy, 'continue' ), 5 );
+} );
+
+QUnit.test( 'createClaim()', function( assert ) {
+ var mock = mockApi();
+
+ mock.api.createClaim( 'entity id', 12345, 'snak type', 'property id',
'snak value' );
+
+ assert.ok( mock.spy.calledOnce, 'Triggered API call.' );
+
+ assert.equal(
+ getParam( mock.spy, 'action' ),
+ 'wbcreateclaim',
+ 'Verified API module being called.'
+ );
+
+ assert.equal( getParam( mock.spy, 'entity' ), 'entity id' );
+ assert.equal( getParam( mock.spy, 'baserevid' ), 12345 );
+ assert.equal( getParam( mock.spy, 'snaktype' ), 'snak type' );
+ assert.equal( getParam( mock.spy, 'property' ), 'property id' );
+ assert.equal( getParam( mock.spy, 'value' ), JSON.stringify( 'snak
value' ) );
+} );
+
+QUnit.test( 'getClaims()', function( assert ) {
+ var mock = mockApi( 'get' );
+
+ mock.api.getClaims( 'entity id', 'property id', 'claim GUID', 'rank',
'claim props' );
+
+ assert.ok( mock.spy.calledOnce, 'Triggered API call.' );
+
+ assert.equal(
+ getParam( mock.spy, 'action' ),
+ 'wbgetclaims',
+ 'Verified API module being called.'
+ );
+
+ assert.equal( getParam( mock.spy, 'entity' ), 'entity id' );
+ assert.equal( getParam( mock.spy, 'property' ), 'property id' );
+ assert.equal( getParam( mock.spy, 'claim' ), 'claim GUID' );
+ assert.equal( getParam( mock.spy, 'rank' ), 'rank' );
+ assert.equal( getParam( mock.spy, 'props' ), 'claim props' );
+} );
+
+QUnit.test( 'setLabel(), setDescription()', function( assert ) {
+ var subjects = ['Label', 'Description'];
+
+ for( var i = 0; i < subjects.length; i++ ) {
+ var mock = mockApi();
+
+ mock.api['set' + subjects[i]]( 'entity id', 12345, 'text',
'language code' );
+
+ assert.ok( mock.spy.calledOnce, 'Triggered API call.' );
+
+ assert.equal(
+ getParam( mock.spy, 'action' ),
+ 'wbset' + subjects[i].toLowerCase(),
+ 'Verified API module being called.'
);
- QUnit.start(); // skip all remaining queued tests
- };
- /**
- * Creates an entity via the API.
- *
- * @param {string} [entityType] Either "item" or "property"
- * @param {Object} [data] Stringified JSON representing the entity
content
- */
- var createEntity = function( entityType, data ) {
- data = data || {};
-
- api.createEntity( entityType, data ).done( function( response )
{
- QUnit.assert.equal(
- response.success,
- 1,
- 'Created ' + entityType + '.'
- );
- entityStack.push( response.entity );
- testrun.dequeue( qkey );
- } ).fail( onFail );
- };
-
- QUnit.module( 'wikibase.api.RepoApi', QUnit.newMwEnvironment( {
- teardown: function() {
- entityStack = [];
- }
- } ) );
-
- // This test does nothing more than creating an empty entity. It would
not need to invoke a
- // queue but can be used as basic template for creating other API tests.
- QUnit.test( 'Create an empty entity', function( assert ) {
- testrun.queue( qkey, function() { createEntity( 'item' ); } );
- // testrun.queue( qkey, function() { ...; testrun.dequeue( qkey
); } );
- runTest();
- } );
-
- QUnit.test( 'Create an empty property', function( assert ) {
- testrun.queue( qkey, function() {
- createEntity( 'property', { datatype: 'string' } );
- } );
- runTest();
- } );
-
- QUnit.test( 'Search for an entity', function( assert ) {
- var data = {
- labels: { de: {
- language: 'de',
- value: 'de-search-val'
- } }
- };
-
- testrun.queue( qkey, function() { createEntity( 'item', data );
} );
-
- testrun.queue( qkey, function() {
- api.searchEntities( data.labels.de.value,
data.labels.de.language, 'item', 2, 0 )
- .done( function( response ) {
-
- assert.ok(
- response.search.length > 0,
- 'Search results returned.'
- );
- assert.equal(
- response.search[0].label,
- data.labels.de.value,
- 'Verified item label.'
- );
-
- testrun.dequeue( qkey );
- } )
- .fail( onFail );
- } );
-
- runTest();
-
- });
-
- QUnit.test( 'Edit an entity', function( assert ) {
- testrun.queue( qkey, function() { createEntity( 'item' ); } );
-
- testrun.queue( qkey, function() {
- var entity = entityStack[0];
-
- var data = {
- labels: {
- de: {
- language: 'de',
- value: 'de-value'
- },
- en: {
- language: 'en',
- value: 'en-value'
- }
- }
- };
-
- api.editEntity( entity.id, entity.lastrevid, data
).done( function( response ) {
-
- assert.equal(
- response.entity.id,
- entity.id,
- 'Verified entity id.'
- );
-
- assert.deepEqual(
- response.entity.labels,
- data.labels,
- 'Edited entity.'
- );
-
- testrun.dequeue( qkey );
- } ).fail( onFail );
-
- } );
-
- runTest();
-
- } );
-
- QUnit.test( 'Create a claim (string value)', function( assert ) {
-
- testrun.queue( qkey, function() { createEntity( 'item' ); } );
- testrun.queue( qkey, function() { createEntity( 'property', {
datatype: 'string' } ); } );
-
- testrun.queue( qkey, function() {
- var entity = entityStack[0],
- property = entityStack[1];
-
- api.createClaim(
- entity.id,
- entity.lastrevid,
- 'value',
- property.id,
- 'This claim is true'
- ).done( function( response ) {
-
- assert.equal(
- response.claim.mainsnak.property,
- property.id,
- 'Verified claim\'s property id.'
- );
-
- testrun.dequeue( qkey );
-
- } ).fail( onFail );
-
- } );
-
- runTest();
-
- } );
-
- QUnit.test( 'Get claim (string value)', function( assert ) {
-
- var answer = '42', entity, property;
-
- testrun.queue( qkey, function() { createEntity( 'item' ); } );
- testrun.queue( qkey, function() { createEntity( 'property', {
datatype: 'string' } ); } );
-
- testrun.queue( qkey, function() {
- entity = entityStack[0];
- property = entityStack[1];
-
- api.createClaim(
- entity.id,
- entity.lastrevid,
- 'value',
- property.id,
- answer
- ).done( function( response ) {
- testrun.dequeue( qkey );
- } ).fail( onFail );
-
- } );
-
- testrun.queue( qkey, function() {
- api.getClaims( entity.id, property.id ).done( function(
response ) {
-
- assert.ok(
- property.id in response.claims,
- 'Claim data for given property found.'
- );
-
- assert.equal(
-
response.claims[property.id][0].mainsnak.datavalue.value,
- answer,
- 'Claim value verified.'
- );
-
- testrun.dequeue( qkey );
-
- } ).fail( onFail );
-
- } );
-
- runTest();
-
- } );
-
- /**
- * Will test the RepoApi's function to set one of the multilingual
basic values, e.g. label or
- * description.
- *
- * @param {Object} assert
- * @param {string} type E.g. 'label' or 'description'
- * @param {string} apiFnName The name of the wikibase.api.RepoApi
function to set the value
- */
- function testSetBasicMultiLingualValue( assert, type, apiFnName ) {
- var entity,
- typesApiPropName = type + 's', // API uses plural form
of the type: 'label' -> 'labels'
- language = 'en',
- value = language + '-' + type; // could be anything
really
-
- testrun.queue( qkey, function() { createEntity( 'item' ); } );
-
- testrun.queue( qkey, function() {
- entity = entityStack[0];
-
- api[ apiFnName ](
- entity.id, entity.lastrevid, value, language
- ).done( function( response ) {
-
- assert.deepEqual(
- response.entity[ typesApiPropName ].en,
- { language: language, value: value },
- type + ' "' + value + '" has been set
for language "' + language + '"'
- );
-
- testrun.dequeue( qkey );
- } ).fail( onFail );
- } );
-
- testrun.queue( qkey, function() {
- var setTypeApiPromise = api[ apiFnName ](
- entity.id, entity.lastrevid, value,
'non-existent-language'
- );
-
- assert.ok( // promise is a plain object, so check for
'then' function
- $.isFunction( setTypeApiPromise.then ),
- 'wikibase.api.RepoApi.' + apiFnName + ' returns
a jQuery Promise'
- );
-
-
- setTypeApiPromise.done( function( response ) {
- assert.ok(
- false,
- 'Impossible to set the ' + type + ' for
an unknown language'
- );
- QUnit.start();
- } );
-
- setTypeApiPromise.fail( function( code, details ) {
- assert.equal(
- code,
- 'unknown_language',
- 'Failed trying to set the value on an
unknown language'
- );
- testrun.dequeue( qkey );
- } );
- } );
-
- runTest();
+ assert.equal( getParam( mock.spy, 'id' ), 'entity id' );
+ assert.equal( getParam( mock.spy, 'baserevid' ), 12345 );
+ assert.equal( getParam( mock.spy, 'value' ), 'text' );
+ assert.equal( getParam( mock.spy, 'language' ), 'language code'
);
}
+} );
- QUnit.test( 'Set label', function( assert ) {
- testSetBasicMultiLingualValue( assert, 'label', 'setLabel' );
- } );
+QUnit.test( 'setAliases()', function( assert ) {
+ var mock = mockApi();
- QUnit.test( 'Set description', function( assert ) {
- testSetBasicMultiLingualValue( assert, 'description',
'setDescription' );
- } );
+ mock.api.setAliases(
+ 'entity id', 12345, ['alias1', 'alias2'], ['alias-remove'],
'language code'
+ );
-// TODO Re-activate test after having solved problem with edit conflict
detection added in change
-// set I344d76812649781c83814afb8e9386ff66fc9086 (commit
3680cedf87a7a45296320b12590432bc50a46c5a)
-/*
- QUnit.test( 'Add and remove site links', function( assert ) {
- var data = {
- sitelinks: {
- dewiki: {
- site: 'dewiki',
- title: 'Königsberg in Bayern',
- badges: [], // this relys on config, so
for now only an empty array
- url:
'http://de.wikipedia.org/wiki/K%C3%B6nigsberg_in_Bayern'
- }
- }
- };
+ assert.ok( mock.spy.calledOnce, 'Triggered API call.' );
- var invalidData = {
- sitelinks: {
- dewiki: {
- site: 'doesnotexist',
- title: 'doesnotexist'
- }
- }
- };
+ assert.equal(
+ getParam( mock.spy, 'action' ),
+ 'wbsetaliases',
+ 'Verified API module being called.'
+ );
- testrun.queue( qkey, function() { createEntity( 'item' ); } );
+ assert.equal( getParam( mock.spy, 'id' ), 'entity id' );
+ assert.equal( getParam( mock.spy, 'baserevid' ), 12345 );
+ assert.equal( getParam( mock.spy, 'add' ), 'alias1|alias2' );
+ assert.equal( getParam( mock.spy, 'remove' ), 'alias-remove' );
+ assert.equal( getParam( mock.spy, 'language' ), ['language code'] );
+} );
- testrun.queue( qkey, function() {
- api.setSitelink(
- entity.id,
- entity.lastrevid,
- data.sitelinks.dewiki.site,
- data.sitelinks.dewiki.title,
- data.sitelinks.dewiki.badges
- ).done( function( response ) {
+QUnit.test( 'setSiteLink()', function( assert ) {
+ var mock = mockApi();
- assert.deepEqual(
-
response.entity.sitelinks[data.sitelinks.dewiki.site],
- data.sitelinks.dewiki,
- 'Set site link.'
- );
+ mock.api.setSitelink(
+ 'entity id', 12345, 'site id', 'page name', ['entity id of
badge1', 'entity id of badge 2']
+ );
- testrun.dequeue( qkey );
- } ).fail( onFail );
- } );
+ assert.ok( mock.spy.calledOnce, 'Triggered API call.' );
- testrun.queue( qkey, function() {
- api.getEntities( entity.id, 'sitelinks' ).done(
function( response ) {
+ assert.equal(
+ getParam( mock.spy, 'action' ),
+ 'wbsetsitelink',
+ 'Verified API module being called.'
+ );
- delete data.sitelinks.dewiki.url;
+ assert.equal( getParam( mock.spy, 'id' ), 'entity id' );
+ assert.equal( getParam( mock.spy, 'baserevid' ), 12345 );
+ assert.equal( getParam( mock.spy, 'linksite' ), 'site id' );
+ assert.equal( getParam( mock.spy, 'linktitle' ), 'page name' );
+ assert.equal( getParam( mock.spy, 'badges' ), 'entity id of
badge1|entity id of badge 2' );
+} );
- assert.deepEqual(
- response.entities[entity.id].sitelinks,
- data.sitelinks,
- 'Verified site link.'
- );
-
- testrun.dequeue( qkey );
- } ).fail( onFail );
- } );
-
- testrun.queue( qkey, function() {
- api.setSitelink(
- entity.id,
- entity.lastrevid,
- invalidData.sitelinks.dewiki.site,
- invalidData.sitelinks.dewiki.title
- ).done( function( response ) {
-
- assert.ok(
- false,
- 'It is possible to set an
invalid site link.'
- );
-
- QUnit.start();
- } ).fail( function( code, details ) {
- assert.equal(
- code,
- 'unknown_linksite',
- 'Failed trying to set an
invalid site link.'
- );
- testrun.dequeue( qkey );
- } );
- } );
-
- testrun.queue( qkey, function() {
- api.getEntities( entity.id, 'sitelinks' ).done(
function( response ) {
-
- assert.deepEqual(
- response.entities[entity.id].sitelinks,
- [],
- 'Verified empty site links.'
- );
-
- testrun.dequeue( qkey );
- } ).fail( onFail );
- } );
-
- runTest();
-
- } );
-*/
- QUnit.test( 'Set aliases', function( assert ) {
- testrun.queue( qkey, function() { createEntity( 'item' ); } );
-
- testrun.queue( qkey, function() {
- api.setAliases(
- entityStack[0].id, entityStack[0].lastrevid, [
'alias1', 'alias2' ], [], 'en'
- ).done( function( response ) {
-
- assert.deepEqual(
- response.entity.aliases.en,
- [
- { language: 'en', value:
'alias1' },
- { language: 'en', value:
'alias2' }
- ],
- 'Added aliases.'
- );
-
- testrun.dequeue( qkey );
- } ).fail( onFail );
- } );
-
- testrun.queue( qkey, function() {
- api.setAliases(
- entityStack[0].id,
- entityStack[0].lastrevid,
- [ 'alias1', 'alias2' ],
- [],
- 'doesnotexist'
- ).done( function( response ) {
-
- assert.ok(
- false,
- 'It is possible to set aliases for an
invalid language.'
- );
-
- QUnit.start();
- } ).fail( function( code, details ) {
- assert.equal(
- code,
- 'unknown_language',
- 'Failed trying to set aliases for an
invalid language.'
- );
- testrun.dequeue( qkey );
- } );
- } );
-
-// TODO Re-activate after having solved problem with edit conflict detection
added in change
-// set I344d76812649781c83814afb8e9386ff66fc9086 (commit
3680cedf87a7a45296320b12590432bc50a46c5a)
-/*
- testrun.queue( qkey, function() {
- api.setAliases(
- entityStack[0].id, entityStack[0].lastrevid,
'alias3', 'alias1', 'en'
- ).done( function( response ) {
-
- assert.deepEqual(
- response.entity.aliases.en,
- [
- { language: 'en', value:
'alias2' },
- { language: 'en', value:
'alias3' }
- ],
- 'Added and removed aliases.'
- );
-
- testrun.dequeue( qkey );
- } ).fail( onFail );
- } );
-
- testrun.queue( qkey, function() {
- api.setAliases(
- entityStack[0].id, entityStack[0].lastrevid,
'', [ 'alias2', 'alias3' ], 'en'
- ).done( function( response ) {
-
- assert.equal(
- response.entity.aliases,
- undefined,
- 'Removed aliases.'
- );
-
- testrun.dequeue( qkey );
- } ).fail( onFail );
- } );
-*/
- runTest();
-
- } );
-
-}( mediaWiki, wikibase, jQuery, QUnit ) );
+}( mediaWiki, wikibase, QUnit, sinon ) );
--
To view, visit https://gerrit.wikimedia.org/r/177503
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I3d1d3af84cf8924115ef38ef319ae09b51bd0ddf
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikibaseJavaScriptApi
Gerrit-Branch: master
Gerrit-Owner: Henning Snater <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits