Jump to content

Module:Mock title/testcases: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
add test for patch functions returning values of their func parameters
add tests to check that titles are not patched before or after patching functions are run
Line 789: Line 789:
constructorName = "new",
constructorName = "new",
constructorArgs = {'Wikipedia:Sandbox'},
constructorArgs = {'Wikipedia:Sandbox'},
expectedMockTitle = MOCK_WP_SANDBOX,
expectedRealTitle = mw.title.new('Wikipedia:Sandbox'),
},
},
{
{
Line 794: Line 796:
constructorName = "makeTitle",
constructorName = "makeTitle",
constructorArgs = {4, 'Sandbox'},
constructorArgs = {4, 'Sandbox'},
expectedMockTitle = MOCK_WP_SANDBOX,
expectedRealTitle = mw.title.new('Wikipedia:Sandbox'),
},
},
{
{
Line 799: Line 803:
constructorName = "getCurrentTitle",
constructorName = "getCurrentTitle",
constructorArgs = {},
constructorArgs = {},
expectedMockTitle = MOCK_WP_SANDBOX,
expectedRealTitle = mw.title.getCurrentTitle(),
},
},
}
}


for _, testData in ipairs(patchingFunctionTestArgs) do
for _, testData in ipairs(patchingFunctionTestArgs) do
suite[
suite[string.format("test %s: %s is patched when the func parameter is run", testData.patchFuncName, testData.constructorName)] = function (self)
string.format(
"test %s: %s is patched when the func parameter is run",
testData.patchFuncName,
testData.constructorName
)
] = function (self)
self:setUpDeregistrationTests()
self:setUpDeregistrationTests()
mMockTitle[testData.patchFuncName](function ()
mMockTitle[testData.patchFuncName](function ()
self:assertTitlesAreEqual(
self:assertTitlesAreEqual(
mw.title[testData.constructorName](unpack(testData.constructorArgs)),
mw.title[testData.constructorName](unpack(testData.constructorArgs)),
testData.expectedMockTitle
MOCK_WP_SANDBOX
)
)
end)
end)
self:tearDownRegistrationTests()
self:tearDownRegistrationTests()
end
end


suite[
string.format(
"test %s: %s is not patched before the function is run",
testData.patchFuncName,
testData.constructorName
)
] = function (self)
-- Setup
self:setUpDeregistrationTests()
local realSandboxTitle = mw.title.new('Wikipedia:Sandbox')
self:assertTitlesAreEqual(
mw.title[testData.constructorName](unpack(testData.constructorArgs)),
testData.expectedRealTitle
)
self:tearDownRegistrationTests()
end

suite[
string.format(
"test %s: %s is not patched after the function is run",
testData.patchFuncName,
testData.constructorName
)
] = function (self)
-- Setup
self:setUpDeregistrationTests()
local realSandboxTitle = mw.title.new('Wikipedia:Sandbox')
mMockTitle[testData.patchFuncName](function () end)
-- Test
self:assertTitlesAreEqual(
mw.title[testData.constructorName](unpack(testData.constructorArgs)),
testData.expectedRealTitle
)
-- Teardown
self:tearDownRegistrationTests()
end
end
end


Line 830: Line 879:
self:assertTitlesAreEqual(mw.title.getCurrentTitle(), MOCK_WP_SANDBOX)
self:assertTitlesAreEqual(mw.title.getCurrentTitle(), MOCK_WP_SANDBOX)
end)
end)
self:tearDownRegistrationTests()
end

suite["test patchTitleConstructors: title constructors are not patched after the function is run"] = function (self)
-- Setup
self:setUpDeregistrationTests()
local realSandboxTitle = mw.title.new('Wikipedia:Sandbox')
local realCurrentTitle = mw.title.getCurrentTitle()
mMockTitle.patchTitleConstructors(function () end)

-- Test
self:assertTitlesAreEqual(
mw.title.new('Wikipedia:Sandbox'),
realSandboxTitle
)
self:assertTitlesAreEqual(
mw.title.makeTitle(4, 'Sandbox'),
realSandboxTitle
)
self:assertTitlesAreEqual(mw.title.getCurrentTitle(), realCurrentTitle)

-- Teardown
self:tearDownRegistrationTests()
self:tearDownRegistrationTests()
end
end

Revision as of 14:12, 22 August 2023

local mMockTitle = require("Module:Mock title")
local ScribuntoUnit = require("Module:ScribuntoUnit")

local suite = ScribuntoUnit:new()

--------------------------------------------------------------------------------
-- Custom assertion methods
--------------------------------------------------------------------------------

function suite:assertTitlesAreEqual(expected, actual)
	local properties = {
		"prefixedText",
		"id",
		"exists",
		"isRedirect",
		"redirectTarget",
		"contentModel",
	}
	for _, prop in ipairs(properties) do
		self:assertEquals(
			expected[prop],
			actual[prop],
			string.format('mismatch in property "%s"', prop)
		)
	end
	
	local deepProperties = {"protectionLevels", "cascadingProtection"}
	for _, prop in ipairs(deepProperties) do
		self:assertDeepEquals(
			expected[prop],
			actual[prop],
			string.format('mismatch in property "%s"', prop)
		)
	end
	
	self:assertEquals(
		not not expected.file,
		not not actual.file,
		string.format(
			'expected %s file property; actual %s file property',
			expected.file and 'has' or 'has no',
			actual.file and 'has' or 'has no'
		)
	)
	
	if expected.file and actual.file then
		local fileProperties = {
			"exists",
			"width",
			"height",
			"size",
			"mimeType",
			"length",
		}
		for _, prop in ipairs(fileProperties) do
			self:assertEquals(
				expected.file[prop],
				actual.file[prop],
				string.format('mismatch in property "file.%s"', prop)
			)
		end
		
		self:assertDeepEquals(
			expected.file.pages,
			actual.file.pages,
			string.format('mismatch in property "%s"', prop)
		)
	end
	
	self:assertEquals(
		expected:getContent(),
		actual:getContent(),
		"mismatch in getContent()"
	)
end

function suite:assertTitlesAreNotEqual(expected, actual)
	local success, result = pcall(self.assertTitlesAreEqual, self, expected, actual)
	if success then
		self:fail(string.format(
			'Failed to assert that the specified titles are not equal (title: %s)',
			tostring(expected)
		))
	end
end

--------------------------------------------------------------------------------
-- MockTitle tests
--------------------------------------------------------------------------------

suite["test MockTitle: when no argument table is supplied, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle() end,
		"bad argument #1 to 'MockTitle' (table expected, got nil)"
	)
end

suite["test MockTitle: when no title argument is supplied, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle{} end,
		"bad named argument title to 'MockTitle' (string or number expected, got nil)"
	)
end

local pageNames = {
	"Example",
	"fr:Example",
	"Module:Sandbox",
	"Module:Sandbox/subpage",
	"mw:Test",
	"fr:b:Example",
}

for _, pageName in ipairs(pageNames) do
	suite[
		string.format(
			'test MockTitle: when using page name "%s", the prefixedText property equals the page name',
			pageName
		)
	] = function (self)
		self:assertEquals(pageName, mMockTitle.MockTitle({title = pageName}).prefixedText)
	end
end

local simplePropertyTestData = {
	{
		property = "id",
		title = "Example",
		value = 123456,
	},
	{
		property = "isRedirect",
		title = "Main Page",
		value = true,
	},
	{
		property = "exists",
		title = "Main Page",
		value = false,
	},
	{
		property = "contentModel",
		title = "Main Page",
		value = "foo",
	},
}

for _, testData in ipairs(simplePropertyTestData) do
	suite[string.format("test MockTitle: property %s is mocked using the %s option", testData.property, testData.property)] = function (self)
		local title = mMockTitle.MockTitle({title = testData.title, [testData.property] = testData.value})
		self:assertEquals(testData.value, title[testData.property])
	end
end

suite["test MockTitle: passing a page name to the redirectTarget option causes redirectTarget to be a MockTitle object with that page name"] = function (self)
	local title = mMockTitle.MockTitle({title = "Example", redirectTarget = "User:Example"})
	self:assertEquals("User:Example", title.redirectTarget.prefixedText)
end

suite["test MockTitle: passing an ID to the redirectTarget option causes redirectTarget to be a MockTitle object with that ID"] = function (self)
	local mainPageId = 15580374
	local title = mMockTitle.MockTitle({title = "Example", redirectTarget = mainPageId})
	self:assertEquals(mainPageId, title.redirectTarget.id)
end

suite["test MockTitle: passing an options table to the redirectTarget option causes redirectTarget to be a MockTitle object with those options"] = function (self)
	local title = mMockTitle.MockTitle({title = "Example", redirectTarget = {title = "User:Example/common.js", contentModel = "wikitext"}})
	self:assertEquals("User:Example/common.js", title.redirectTarget.prefixedText)
	self:assertEquals("wikitext", title.redirectTarget.contentModel)
end

suite["test MockTitle: passing a MockTitle object to the redirectTarget option causes redirectTarget to be that MockTitle object"] = function (self)
	local mockRedirectTarget = mMockTitle.MockTitle({title = "User:Example/common.js", contentModel = "wikitext"})
	local title = mMockTitle.MockTitle({title = "Example", redirectTarget = mockRedirectTarget})
	self:assertEquals("User:Example/common.js", title.redirectTarget.prefixedText)
	self:assertEquals("wikitext", title.redirectTarget.contentModel)
end

local protectionLevelTestData = {
	{
		optionName = "editProtection",
		optionValue = "sysop",
		expectedProtectionAction = "edit",
	},
	{
		optionName = "moveProtection",
		optionValue = "sysop",
		expectedProtectionAction = "move",
	},
	{
		optionName = "createProtection",
		optionValue = "sysop",
		expectedProtectionAction = "create",
	},
	{
		optionName = "uploadProtection",
		optionValue = "sysop",
		expectedProtectionAction = "upload",
	},
}

for _, testData in ipairs(protectionLevelTestData) do
	suite[
		string.format(
			'test MockTitle: when setting option %s to "%s", the protectionLevels table is updated accordingly',
			testData.optionName,
			testData.optionValue
		)
	] = function (self)
		local title = mMockTitle.MockTitle{title = "Example", [testData.optionName] = testData.optionValue}
		self:assertEquals(testData.optionValue, title.protectionLevels[testData.expectedProtectionAction][1])
	end
end

local cascadingProtectionLevelTestData = {
	{
		optionName = "cascadingEditProtection",
		optionValue = "sysop",
		expectedProtectionAction = "edit",
	},
	{
		optionName = "cascadingMoveProtection",
		optionValue = "sysop",
		expectedProtectionAction = "move",
	},
	{
		optionName = "cascadingCreateProtection",
		optionValue = "sysop",
		expectedProtectionAction = "create",
	},
	{
		optionName = "cascadingUploadProtection",
		optionValue = "sysop",
		expectedProtectionAction = "upload",
	},
}

for _, testData in ipairs(cascadingProtectionLevelTestData) do
	suite[
		string.format(
			'test MockTitle: when setting option %s to "%s", '
			.. 'and when setting the cascadingProtectionSources option, '
			.. 'the cascadingProtection table is updated accordingly',
			testData.optionName,
			testData.optionValue
		)
	] = function (self)
		local title = mMockTitle.MockTitle{
			title = "Example",
			[testData.optionName] = testData.optionValue,
			cascadingProtectionSources = {"Example", "Example 2"},
		}
		self:assertEquals(
			testData.optionValue,
			title.cascadingProtection.restrictions[testData.expectedProtectionAction][1]
		)
		self:assertDeepEquals(
			{"Example", "Example 2"},
			title.cascadingProtection.sources
		)
	end
end

suite["test MockTitle: when a cascading protection argument is given, but no cascading protection sources are given, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle{title = "Example", cascadingEditProtection = "sysop"} end,
		"a cascading protection argument was given but the cascadingProtectionSources argument was missing or empty"
	)
end

suite["test MockTitle: when a cascading protection argument is given, but the cascading protection sources table is empty, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle{title = "Example", cascadingEditProtection = "sysop", cascadingProtectionSources = {}} end,
		"a cascading protection argument was given but the cascadingProtectionSources argument was missing or empty"
	)
end

suite["test MockTitle: when cascading protection sources are given, but no cascading protection argument is given, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle{title = "Example", cascadingProtectionSources = {"Example 2", "Example 3"}} end,
		"the cascadingProtectionSources argument was given, but no cascading protection argument was given"
	)
end

suite['test MockTitle: if the content option is provided, getContent() returns the specified string'] = function (self)
	local title = mMockTitle.MockTitle{title = "Non-existent page 29dh12yxm", content = "some [[wikitext]] content"}
	self:assertEquals("some [[wikitext]] content", title:getContent())
end

local filePropertyTestData = {
	{
		property = "exists",
		optionName = "fileExists",
		optionValue = true,
	},
	{
		property = "width",
		optionName = "fileWidth",
		optionValue = 123,
	},
	{
		property = "height",
		optionName = "fileHeight",
		optionValue = 456,
	},
	{
		property = "pages",
		optionName = "filePages",
		optionValue =  {{height = 800, width = 600}, {height = 800, width = 600}},
	},
	{
		property = "size",
		optionName = "fileSize",
		optionValue = 1024,
	},
	{
		property = "mimeType",
		optionName = "fileMimeType",
		optionValue = "image/png",
	},
	{
		property = "length",
		optionName = "fileLength",
		optionValue = 60,
	},
}

for _, testData in ipairs(filePropertyTestData) do
	suite[string.format(
			'test MockTitle: when setting option %s to "%s", the file table is updated accordingly',
			testData.optionName,
			tostring(testData.optionValue)
	)] = function(self)
		local title = mMockTitle.MockTitle{title = "File:Non-existent hf893bc0.png", [testData.optionName] = testData.optionValue}
		self:assertEquals(testData.optionValue, title.file[testData.property])
	end
end

suite["test MockTitle: when setting fileExists in a non-file namespace, no file table is set"] = function(self)
	local title = mMockTitle.MockTitle{title = "Non-existent page 34u8wg90bfr", fileExists = true}
	self:assertEquals(nil, title.file)
end

local fileExistsTestData = {
	{
		title = "Non-existent article tr9w78ebna0",
		expected = nil,
	},
	{
		title = "Talk:Non-existent talk page 34hdbe0pafj",
		expected = nil,
	},
	{
		title = 	"File:Non-existent file 341gh87fgg8",
		expected = true,
	},
	{
		title = "Media:Non-existent file pbfhrw3v8d",
		expected = true,
	},
}

for _, testData in ipairs(fileExistsTestData) do
	suite[
		string.format(
			'test MockTitle: when using page "%s", the fileExists property is %s',
			testData.title,
			testData.expected and "set" or "not set"
		)
	] = function (self)
		local title = mMockTitle.MockTitle({title = testData.title, fileExists = true})
		self:assertEquals(testData.expected, title.fileExists)
	end
end

local fallbackPropertyTestData = {
	{
		property = "id",
		option = "id",
		title = "Example",
	},
	{
		property = "isRedirect",
		option = "isRedirect",
		title = "WP:ANI",
	},
	{
		property = "exists",
		option = "exists",
		title = "Non-existent title f292umz0tyi",
	},
	{
		property = "contentModel",
		option = "contentModel",
		title = "User:Example/common.js",
	},
	{
		property = "redirectTarget",
		option = "redirectTarget",
		title = "WP:ANI",
	},
	{
		property = "protectionLevels",
		option = "editProtection",
		title = "Main Page",
	},
	{
		property = "redirectTarget",
		option = "redirectTarget",
		title = "WP:ANI",
	},
	{
		property = "cascadingProtection",
		option = "cascadingEditProtection",
		title = "Main Page",
	},
	{
		property = "cascadingProtection",
		option = "cascadingProtectionSources",
		title = "Main Page",
	},
}

for _, testData in ipairs(fallbackPropertyTestData) do
	suite[string.format("test MockTitle: if no %s option is specified, the real %s value is used", testData.option, testData.property)] = function (self)
		suite:assertDeepEquals(
			mw.title.new(testData.title)[testData.property],
			mMockTitle.MockTitle{title = testData.title}[testData.property]
		)
	end
end

suite["test MockTitle: if no content option is specified, getContent() returns the the real content"] = function (self)
	suite:assertEquals(
		mw.title.new("Main Page"):getContent(),
		mMockTitle.MockTitle{title = "Main Page"}:getContent()
	)
end

suite["test MockTitle: the content property is not shared between mock objects"] = function (self)
	suite:assertNotEquals(
		mMockTitle.MockTitle{title = "Foo", content = "Bar"}:getContent(),
		mMockTitle.MockTitle{title = "Foo", content = "Baz"}:getContent()
	)
end

--------------------------------------------------------------------------------
-- patchTitleObject tests
--------------------------------------------------------------------------------

suite["test patchTitleObject: patchTitleObject returns an equivalent object to MockTitle when used on the same title"] = function (self)
	suite:assertTitlesAreEqual(
		mMockTitle.MockTitle{
			title = "Main Page",
			isRedirect = true,
			redirectTarget = "Wikipedia:Village stocks",
			content = '#REDIRECT: [[Wikipedia:Village stocks]]',
		},
		mMockTitle.patchTitleObject(
			mw.title.new('Main Page'),
			{
				isRedirect = true,
				redirectTarget = "Wikipedia:Village stocks",
				content = "#REDIRECT: [[Wikipedia:Village stocks]]",
			}
		)
	)
end

suite["test patchTitleObject: title arguments are ignored"] = function (self)
	self:assertEquals(
		'User:Example',
		mMockTitle.patchTitleObject(
			mw.title.new('User:Example'),
			{title = "Wikipedia:Sandbox"}
		).prefixedText
	)
end

--------------------------------------------------------------------------------
-- Tests for registering/deregistering mock titles
--------------------------------------------------------------------------------

local MOCK_WP_SANDBOX_ARGS = {
	title = 'Wikipedia:Sandbox',
	content = 'Some random content: zbS8mJ31l8eY',
	isRedirect = true -- Cannot be true for a real page with the above content, so we can prove it's a mock
}
local MOCK_WP_SANDBOX = mMockTitle.MockTitle(MOCK_WP_SANDBOX_ARGS)

local MOCK_MAIN_PAGE_ARGS = {
	title = 'Main Page',
	content = 'Mock main page content: 38fgbhqwu8adb',
}
local MOCK_MAIN_PAGE = mMockTitle.MockTitle(MOCK_MAIN_PAGE_ARGS)

function suite:tearDownRegistrationTests()
	mMockTitle.clearAllMockTitles()
end

suite["test registerMockTitle: real titles are used during patching when no mocks are registered"] = function (self)
	-- Setup
	local realTitle = mw.title.new('Wikipedia:Sandbox')

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			realTitle
		)
	end)
	
	-- Teardown
	self:tearDownRegistrationTests()
end

local registerMockTitleTestData = {
	{argType = 'option table', arg = MOCK_WP_SANDBOX_ARGS},
	{argType = 'mock title', arg = MOCK_WP_SANDBOX},
}

for _, testData in ipairs(registerMockTitleTestData) do
	suite[string.format(
		"test registerMockTitle: mocks are registered when specified using %s",
		testData.argType
	)] = function (self)
		-- Setup
		mMockTitle.registerMockTitle(testData.arg)

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
				MOCK_WP_SANDBOX
			)
		end)
		
		-- Teardown
		self:tearDownRegistrationTests()
	end
end

for _, testData in ipairs(registerMockTitleTestData) do
	suite[string.format(
		"test registerMockCurrentTitle: mocks are registered when specified using %s",
		testData.argType
	)] = function (self)
		-- Setup
		mMockTitle.registerMockCurrentTitle(testData.arg)

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.getCurrentTitle(),
				MOCK_WP_SANDBOX
			)
		end)
		
		-- Teardown
		self:tearDownRegistrationTests()
	end
end

function suite:setUpDeregistrationTests()
	mMockTitle.registerMockTitles(MOCK_WP_SANDBOX_ARGS,	MOCK_MAIN_PAGE_ARGS)
	mMockTitle.registerMockCurrentTitle(MOCK_WP_SANDBOX_ARGS)
end

suite["test deregisterMockTitle: previously registered titles are not deregistered if a deregistration function is not called"] = function (self)
	-- Setup
	self:setUpDeregistrationTests()

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			MOCK_WP_SANDBOX
		)
	end)

	-- Teardown
	self:tearDownRegistrationTests()
end

local deregisterMockTitleTestData = {
	{argType = "title", arg = MOCK_WP_SANDBOX_ARGS.title},
	{argType = "args table", arg = MOCK_WP_SANDBOX_ARGS},
	{argType = "ID", arg = MOCK_WP_SANDBOX.id},
	{argType = "title object", arg = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)},
	{argType = "mock title object", arg = MOCK_WP_SANDBOX},
}

for _, testData in ipairs(deregisterMockTitleTestData) do
	suite[
		string.format(
			"test deregisterMockTitle: previously registered titles are deregistered when specified by %s",
			testData.argType
		)
	] = function (self)
		-- Setup
		self:setUpDeregistrationTests()
		local realTitle = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)
		mMockTitle.deregisterMockTitle(testData.arg)

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
				realTitle
			)
		end)

		-- Teardown
		self:tearDownRegistrationTests()
	end
end

suite["test deregisterMockCurrentTitle: the previously registered current title is not deregistered if a deregistration function is not called"] = function (self)
	-- Setup
	self:setUpDeregistrationTests()

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(mw.title.getCurrentTitle(), MOCK_WP_SANDBOX)
	end)

	-- Teardown
	self:tearDownRegistrationTests()
end

suite["test deregisterMockCurrentTitle: the previously registered current title is deregistered if deregisterMockCurrentTitle is called"] = function (self)
	-- Setup
	self:setUpDeregistrationTests()
	local realTitle = mw.title.getCurrentTitle()
	mMockTitle.deregisterMockCurrentTitle()

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(mw.title.getCurrentTitle(), realTitle)
	end)

	-- Teardown
	self:tearDownRegistrationTests()
end

local registerMockTitlesTestData = {
	{argType = 'option table', args = {MOCK_WP_SANDBOX_ARGS, MOCK_MAIN_PAGE_ARGS}},
	{argType = 'mock title', args = {MOCK_WP_SANDBOX, MOCK_MAIN_PAGE}},
}

for _, testData in ipairs(registerMockTitlesTestData) do
	suite[string.format(
		"test registerMockTitles: mocks are registered when specified using %s",
		testData.argType
	)] = function (self)
		-- Setup
		mMockTitle.registerMockTitles(unpack(testData.args))

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
				MOCK_WP_SANDBOX
			)
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
				MOCK_MAIN_PAGE
			)
		end)
		
		-- Teardown
		self:tearDownRegistrationTests()
	end
end

local deregisterMockTitlesTestData = {
	{
		argType = "title",
		args = {MOCK_WP_SANDBOX_ARGS.title, MOCK_MAIN_PAGE_ARGS.title},
	},
	{argType = "args table", args = {MOCK_WP_SANDBOX_ARGS, MOCK_MAIN_PAGE_ARGS}},
	{argType = "ID", args = {MOCK_WP_SANDBOX.id, MOCK_MAIN_PAGE.id}},
	{
		argType = "title object",
		args = {
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
		},
	},
	{argType = "mock title object", args = {MOCK_WP_SANDBOX, MOCK_MAIN_PAGE}},
}

for _, testData in ipairs(deregisterMockTitlesTestData) do
	suite[
		string.format(
			"test deregisterMockTitle: previously registered titles are deregistered when specified by %s",
			testData.argType
		)
	] = function (self)
		-- Setup
		self:setUpDeregistrationTests()
		local realSandboxTitle = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)
		local realMainPageTitle = mw.title.new(MOCK_MAIN_PAGE_ARGS.title)
		mMockTitle.deregisterMockTitles(unpack(testData.args))

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
				realSandboxTitle
			)
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
				realMainPageTitle
			)
		end)

		-- Teardown
		self:tearDownRegistrationTests()
	end
end

suite["test clearMockTitleRegistry: all mock titles are cleared"] = function (self)
	-- Setup
	self:setUpDeregistrationTests()
	local realSandboxTitle = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)
	local realMainPageTitle = mw.title.new(MOCK_MAIN_PAGE_ARGS.title)
	mMockTitle.clearMockTitleRegistry()

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			realSandboxTitle
		)
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
			realMainPageTitle
		)
	end)

	-- Teardown
	self:tearDownRegistrationTests()
end

suite["test clearAllMockTitles: all registered titles are deregistered when clearAllMockTitles is called"] = function (self)
	-- Setup
	self:setUpDeregistrationTests()
	local realSandboxTitle = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)
	local realMainPageTitle = mw.title.new(MOCK_MAIN_PAGE_ARGS.title)
	local realCurrentTitle = mw.title.getCurrentTitle()
	mMockTitle.clearAllMockTitles()

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			realSandboxTitle
		)
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
			realMainPageTitle
		)
		self:assertTitlesAreEqual(mw.title.getCurrentTitle(), realCurrentTitle)
	end)

	-- Teardown
	self:tearDownRegistrationTests()
end

suite["test registered mocks do not share content with standalone mocks when title constructors are patched"] = function (self)
	mMockTitle.registerMockTitle{title = 'Wikipedia:Sandbox', content = 'Foo'}
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreNotEqual(
			mw.title.new('Wikipedia:Sandbox'),
			mMockTitle.MockTitle{title = 'Wikipedia:Sandbox', content = 'Bar'}
		)
	end)
	self:tearDownRegistrationTests()
end

--------------------------------------------------------------------------------
-- Tests for patching functions
--------------------------------------------------------------------------------

local patchingFunctionTestArgs = {
	{
		patchFuncName = "patchTitleNew",
		constructorName = "new",
		constructorArgs = {'Wikipedia:Sandbox'},
		expectedMockTitle = MOCK_WP_SANDBOX,
		expectedRealTitle = mw.title.new('Wikipedia:Sandbox'),
	},
	{
		patchFuncName = "patchMakeTitle",
		constructorName = "makeTitle",
		constructorArgs = {4, 'Sandbox'},
		expectedMockTitle = MOCK_WP_SANDBOX,
		expectedRealTitle = mw.title.new('Wikipedia:Sandbox'),
	},
	{
		patchFuncName = "patchGetCurrentTitle",
		constructorName = "getCurrentTitle",
		constructorArgs = {},
		expectedMockTitle = MOCK_WP_SANDBOX,
		expectedRealTitle = mw.title.getCurrentTitle(),
	},
}

for _, testData in ipairs(patchingFunctionTestArgs) do
	suite[
		string.format(
			"test %s: %s is patched when the func parameter is run",
			testData.patchFuncName,
			testData.constructorName
		)
	] = function (self)
		self:setUpDeregistrationTests()
		mMockTitle[testData.patchFuncName](function ()
			self:assertTitlesAreEqual(
				mw.title[testData.constructorName](unpack(testData.constructorArgs)),
				testData.expectedMockTitle
			)
		end)
		self:tearDownRegistrationTests()
	end

	suite[
		string.format(
			"test %s: %s is not patched before the function is run",
			testData.patchFuncName,
			testData.constructorName
		)
	] = function (self)
		-- Setup
		self:setUpDeregistrationTests()
		local realSandboxTitle = mw.title.new('Wikipedia:Sandbox')
		self:assertTitlesAreEqual(
			mw.title[testData.constructorName](unpack(testData.constructorArgs)),
			testData.expectedRealTitle
		)
		self:tearDownRegistrationTests()
	end

	suite[
		string.format(
			"test %s: %s is not patched after the function is run",
			testData.patchFuncName,
			testData.constructorName
		)
	] = function (self)
		-- Setup
		self:setUpDeregistrationTests()
		local realSandboxTitle = mw.title.new('Wikipedia:Sandbox')
		mMockTitle[testData.patchFuncName](function () end)
		
		-- Test
		self:assertTitlesAreEqual(
			mw.title[testData.constructorName](unpack(testData.constructorArgs)),
			testData.expectedRealTitle
		)
		
		-- Teardown
		self:tearDownRegistrationTests()
	end
end

suite["test patchTitleConstructors: all title constructors are patched when the func parameter is run"] = function (self)
	self:setUpDeregistrationTests()
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			MOCK_WP_SANDBOX
		)
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
			MOCK_MAIN_PAGE
		)
		self:assertTitlesAreEqual(mw.title.getCurrentTitle(), MOCK_WP_SANDBOX)
	end)
	self:tearDownRegistrationTests()
end

suite["test patchTitleConstructors: title constructors are not patched after the function is run"] = function (self)
	-- Setup
	self:setUpDeregistrationTests()
	local realSandboxTitle = mw.title.new('Wikipedia:Sandbox')
	local realCurrentTitle = mw.title.getCurrentTitle()
	mMockTitle.patchTitleConstructors(function () end)

	-- Test
	self:assertTitlesAreEqual(
		mw.title.new('Wikipedia:Sandbox'),
		realSandboxTitle
	)
	self:assertTitlesAreEqual(
		mw.title.makeTitle(4, 'Sandbox'),
		realSandboxTitle
	)
	self:assertTitlesAreEqual(mw.title.getCurrentTitle(), realCurrentTitle)

	-- Teardown
	self:tearDownRegistrationTests()
end

local patchingFuncNames = {
	"patchTitleNew",
	"patchMakeTitle",
	"patchGetCurrentTitle",
	"patchTitleConstructors",
}

for _, patchFuncName in ipairs(patchingFuncNames) do
	suite[string.format("test %s: varargs are passed as arguments to the func parameter", patchFuncName)] = function (self)
		mMockTitle[patchFuncName](
			function (a, b, c)
				self:assertEquals("foo", a)
				self:assertEquals("bar", b)
				self:assertEquals("baz", c)
			end,
			"foo",
			"bar",
			"baz"
		)
	end
	
	suite[string.format("test %s: the result of func is returned", patchFuncName)] = function (self)
		local result = mMockTitle[patchFuncName](function ()
			return "some value"
		end)
		self:assertEquals("some value", result)
	end
end

return suite