Skip to content
This repository has been archived by the owner on Sep 14, 2024. It is now read-only.

Commit

Permalink
Added beforeAll, beforeEach, afterEach, afterAll lifecycle hooks for …
Browse files Browse the repository at this point in the history
…testing (#57)

* Rough implementation of lifecycle hooks (before/after (each))

* always run after hooks

* tests are failing, but we have a new test now

* its functional with basic tests passing

* refactored lifecycle hooks to use runTestPlan helper function

* update luacheckrc to accept testEZ globals

* clean up lifecycleHooks.lua some more

* beforeAll, beforeEach, and afterEach throwing will fail test node

* fixed single quotes to double quotes

* reorganized LifecycleHooks.lua

* link to jest spec test

* formatting in TestRunner

* moves lifecycleHooks error tests to end

* Added entry to CHANGELOG.md

* Cleans up formatting with single to double quotes

* comment on intentially missing afterAll

* Consistent hook prefix

* updated luacheckrc to focus testez globals on lifecycleHooks.lua only

* remove extra new line at end of files

* remove stack from lifecycleHooks.lua

* add runNode function in TestRunner to simplify statement and add early returns

* changed assertion into error for string formatting reasons

Co-authored-by: Andrew Moss <andrew@mossage.co.uk>
  • Loading branch information
2 people authored and LPGhatguy committed Jan 21, 2020
1 parent 77cd4bc commit 5fa229b
Show file tree
Hide file tree
Showing 9 changed files with 553 additions and 50 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/luacov.*
/site
/site
/lua_install
10 changes: 10 additions & 0 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@ stds.roblox = {
}
}

stds.testez = {
read_globals = {
"it", "describe", "beforeAll", "beforeEach", "afterAll", "afterEach",
},
}

std = "lua51+roblox"

ignore = {
"212", -- Unused argument, which triggers on unused 'self' too
}

files["tests/lifecycleHooks.lua"] = {
std = "+testez",
}
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# TestEZ Changelog

## Current master
* No changes
* Added beforeAll, beforeEach, afterEach, afterAll lifecycle hooks for testing
* The setup and teardown behavior of these hooks attempt to reach feature parity with [jest](https://jestjs.io/docs/en/setup-teardown).


## 0.1.0 (2019-11-01)
* Initial release.
* Initial release.
169 changes: 169 additions & 0 deletions lib/LifecycleHooks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
local TestEnum = require(script.Parent.TestEnum)

local LifecycleHooks = {}
LifecycleHooks.__index = LifecycleHooks

function LifecycleHooks.new()
local self = {
_stack = {},
}
setmetatable(self, LifecycleHooks)
return self
end

--[[
Returns an array of `beforeEach` hooks in FIFO order
]]
function LifecycleHooks:getBeforeEachHooks()
local key = TestEnum.NodeType.BeforeEach
local hooks = {}

for _, level in ipairs(self._stack) do
for _, hook in ipairs(level[key]) do
table.insert(hooks, hook)
end
end

return hooks
end

--[[
Returns an array of `afterEach` hooks in FILO order
]]
function LifecycleHooks:getAfterEachHooks()
local key = TestEnum.NodeType.AfterEach
local hooks = {}
for _, level in ipairs(self._stack) do
for _, hook in ipairs(level[key]) do
table.insert(hooks, 1, hook)
end
end

return hooks
end

--[[
Pushes uncalled beforeAll and afterAll hooks back up the stack
]]
function LifecycleHooks:popHooks()
local popped = self._stack[#self._stack]
table.remove(self._stack, #self._stack)

local function pushHooksUp(type)

local back = self:_getBackOfStack()

if not back then
return
end

back[type] = popped[type]
end

pushHooksUp(TestEnum.NodeType.BeforeAll)
pushHooksUp(TestEnum.NodeType.AfterAll)
end

function LifecycleHooks:pushHooksFrom(planNode)
assert(planNode ~= nil)

table.insert(self._stack, {
[TestEnum.NodeType.BeforeAll] = self:_getBeforeAllHooksUncalledAtCurrentLevel(planNode.children),
[TestEnum.NodeType.AfterAll] = self:_getAfterAllHooksUncalledAtCurrentLevel(planNode.children),
[TestEnum.NodeType.BeforeEach] = self:_getHooksOfType(planNode.children, TestEnum.NodeType.BeforeEach),
[TestEnum.NodeType.AfterEach] = self:_getHooksOfType(planNode.children, TestEnum.NodeType.AfterEach),
})
end

function LifecycleHooks:getPendingBeforeAllHooks()
return self:_getAndClearPendingHooks(TestEnum.NodeType.BeforeAll)
end

function LifecycleHooks:getAfterAllHooks()
if #self._stack > 0 then
return self:_getAndClearPendingHooks(TestEnum.NodeType.AfterAll)
else
return {}
end
end

--[[
Return any hooks that have not yet been returned for this key and clear those hooks
]]
function LifecycleHooks:_getAndClearPendingHooks(key)
assert(key ~= nil)

if #self._stack > 0 then

local back = self._stack[#self._stack]

local hooks = back[key]

back[key] = {}

return hooks
else
return {}
end
end

--[[
Transfers uncalled beforeAll and afterAll hooks down the stack
]]
function LifecycleHooks:_getBeforeAllHooksUncalledAtCurrentLevel(childNodes)
local hookType = TestEnum.NodeType.BeforeAll
local hooks = self:_getHooksOfTypeFromBackOfStack(hookType)

for _, hook in pairs(self:_getHooksOfType(childNodes, hookType)) do
table.insert(hooks, hook)
end

return hooks
end

function LifecycleHooks:_getAfterAllHooksUncalledAtCurrentLevel(childNodes)
local hookType = TestEnum.NodeType.AfterAll
local hooks = self:_getHooksOfTypeFromBackOfStack(hookType)

for _, hook in pairs(self:_getHooksOfType(childNodes, hookType)) do
table.insert(hooks, 1, hook)
end

return hooks
end

function LifecycleHooks:_getHooksOfTypeFromBackOfStack(hookType)
assert(hookType, "Expected hookType to be an argument")

local currentBack = self:_getBackOfStack()

local hooks = {}

if currentBack then
for _, hook in pairs(currentBack[hookType]) do
table.insert(hooks, hook)
end

currentBack[hookType] = {}
end

return hooks
end

function LifecycleHooks:_getBackOfStack()
return self._stack[#self._stack] or nil
end

function LifecycleHooks:_getHooksOfType(nodes, type)
local hooks = {}

for _, node in pairs(nodes) do
if node.type == type then
table.insert(hooks, node.callback)
end
end

return hooks
end

return LifecycleHooks
6 changes: 5 additions & 1 deletion lib/TestEnum.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ TestEnum.TestStatus = {
TestEnum.NodeType = {
Try = "Try",
Describe = "Describe",
It = "It"
It = "It",
BeforeAll = "BeforeAll",
AfterAll = "AfterAll",
BeforeEach = "BeforeEach",
AfterEach = "AfterEach"
}

TestEnum.NodeModifier = {
Expand Down
21 changes: 21 additions & 0 deletions lib/TestPlanner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,27 @@ function TestPlanner.createEnvironment(builder, extraEnvironment)
builder:popNode()
end

-- Incrementing counter used to ensure that beforeAll, afterAll, beforeEach, afterEach have unique phrases
local lifecyclePhaseId = 0

local lifecycleHooks = {
[TestEnum.NodeType.BeforeAll] = "beforeAll",
[TestEnum.NodeType.AfterAll] = "afterAll",
[TestEnum.NodeType.BeforeEach] = "beforeEach",
[TestEnum.NodeType.AfterEach] = "afterEach"
}

for nodeType, name in pairs(lifecycleHooks) do
env[name] = function(callback)
local node = builder:pushNode(name .. "_" .. tostring(lifecyclePhaseId), nodeType)
lifecyclePhaseId = lifecyclePhaseId + 1

node.callback = callback

builder:popNode()
end
end

function env.itFOCUS(phrase, callback)
local node = builder:pushNode(phrase, TestEnum.NodeType.It, TestEnum.NodeModifier.Focus)

Expand Down
4 changes: 2 additions & 2 deletions lib/TestResults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,15 @@ function TestResults:visualize(root, level)
child.planNode.phrase
)

if #child.messages > 0 then
if child.messages and #child.messages > 0 then
str = str .. "\n " .. (" "):rep(3 * level) .. table.concat(child.messages, "\n " .. (" "):rep(3 * level))
end

table.insert(buffer, str)
else
local str = ("%s%s"):format(
(" "):rep(3 * level),
child.planNode.phrase
child.planNode.phrase or ""
)

if child.status then
Expand Down
Loading

0 comments on commit 5fa229b

Please sign in to comment.