Skip to content

Commit

Permalink
Fix beforeAll/beforeEach not working as expected
Browse files Browse the repository at this point in the history
  • Loading branch information
luttje committed Dec 4, 2024
1 parent 9e4f57d commit 055616b
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 114 deletions.
209 changes: 110 additions & 99 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,118 +112,129 @@ See [the Jestronaut Garry's Mod addon README](./gmod-addon/README.md) for more i
Jestronaut tries to match [the API of Jest](https://jestjs.io/docs/api) as closely as possible. Here are some examples of how to use it:
```lua
describe("test organization into suites", function()
it("should confirm basic math", function()
expect(1 + 1):toBe(2)
describe('readme examples', function()
local ranBeforeAll = 0
beforeAll(function()
ranBeforeAll = ranBeforeAll + 1
end)
end)
it("should let sums NOT match", function()
expect(1 + 1)['not']:toBe(3)
expect(1 + 5)['not']:toBeGreaterThan(7)
end)
describe("test organization into suites", function()
it("should confirm basic math", function()
expect(1 + 1):toBe(2)
end)
end)
it("should have all the matchers Jest has", function()
expect(1 + 1):toBe(2)
expect(0.1 + 5.2):toBeCloseTo(5.3)
expect({}):toBeDefined()
expect(nil):toBeFalsy()
expect(1 + 1):toBeGreaterThan(1)
expect(1 + 1):toBeGreaterThanOrEqual(2)
expect(1 + 1):toBeLessThan(3)
expect(1 + 1):toBeLessThanOrEqual(2)
expect(0/0):toBeNaN()
expect(nil):toBeNil()
expect(nil):toBeNull()
expect(1 + 1):toBeTruthy()
expect(1 + 1):toBeType('number')
expect(nil):toBeUndefined()
expect({1, 2, 3}):toContain(2)
expect({1, 2, 3}):toContainEqual(2)
expect({1, 2, 3}):toEqual({1, 2, 3})
expect({1, 2, 3}):toHaveLength(3)
expect({
a = 1,
b = 2,
c = 3
}):toHaveProperty('a')
expect("abc"):toMatch("c$") -- Lua patterns
expect({
a = 1,
b = 2,
c = 3
}):toMatchObject({
a = 1,
b = 2
})
expect({}):toStrictEqual({})
expect(function() error('test') end):toThrow('test')
expect(function() error('testing') end):toThrowError('testing')
end)
it("should let sums NOT match", function()
expect(1 + 1)['not']:toBe(3)
expect(1 + 5)['not']:toBeGreaterThan(7)
end)
it('should be able to mock function implementations', function()
local mockFn = jestronaut:fn(function() return 'x', 'y', 'z' end)
mockFn(1, 2, 3)

expect(mockFn):toHaveBeenCalled()
expect(mockFn):toHaveBeenCalledTimes(1)
expect(mockFn):toHaveBeenCalledWith(1, 2, 3)

mockFn(3, 2, 1)
expect(mockFn):toHaveBeenLastCalledWith(3, 2, 1)
expect(mockFn):toHaveBeenNthCalledWith(1, 1, 2, 3)
expect(mockFn):toHaveLastReturnedWith('x', 'y', 'z')
expect(mockFn):toHaveNthReturnedWith(1, 'x', 'y', 'z')
expect(mockFn):toHaveReturned()
expect(mockFn):toHaveReturnedTimes(2)
expect(mockFn):toHaveReturnedWith('x', 'y', 'z')
end)
it("should have all the matchers Jest has", function()
expect(1 + 1):toBe(2)
expect(0.1 + 5.2):toBeCloseTo(5.3)
expect({}):toBeDefined()
expect(nil):toBeFalsy()
expect(1 + 1):toBeGreaterThan(1)
expect(1 + 1):toBeGreaterThanOrEqual(2)
expect(1 + 1):toBeLessThan(3)
expect(1 + 1):toBeLessThanOrEqual(2)
expect(0 / 0):toBeNaN()
expect(nil):toBeNil()
expect(nil):toBeNull()
expect(1 + 1):toBeTruthy()
expect(1 + 1):toBeType('number')
expect(nil):toBeUndefined()
expect({ 1, 2, 3 }):toContain(2)
expect({ 1, 2, 3 }):toContainEqual(2)
expect({ 1, 2, 3 }):toEqual({ 1, 2, 3 })
expect({ 1, 2, 3 }):toHaveLength(3)
expect({
a = 1,
b = 2,
c = 3
}):toHaveProperty('a')
expect("abc"):toMatch("c$") -- Lua patterns
expect({
a = 1,
b = 2,
c = 3
}):toMatchObject({
a = 1,
b = 2
})
expect({}):toStrictEqual({})
expect(function() error('test') end):toThrow('test')
expect(function() error('testing') end):toThrowError('testing')
end)
local ranBefore = 0
beforeAll(function()
ranBefore = ranBefore + 1
end)
it('should be able to mock function implementations', function()
local mockFn = jestronaut:fn(function() return 'x', 'y', 'z' end)
mockFn(1, 2, 3)

expect(mockFn):toHaveBeenCalled()
expect(mockFn):toHaveBeenCalledTimes(1)
expect(mockFn):toHaveBeenCalledWith(1, 2, 3)

mockFn(3, 2, 1)
expect(mockFn):toHaveBeenLastCalledWith(3, 2, 1)
expect(mockFn):toHaveBeenNthCalledWith(1, 1, 2, 3)
expect(mockFn):toHaveLastReturnedWith('x', 'y', 'z')
expect(mockFn):toHaveNthReturnedWith(1, 'x', 'y', 'z')
expect(mockFn):toHaveReturned()
expect(mockFn):toHaveReturnedTimes(2)
expect(mockFn):toHaveReturnedWith('x', 'y', 'z')
end)

it('should run beforeAll for each "it" this checks how many (so far)', function()
expect(ranBefore):toEqual(4)
end)
local ranBefore = 0
beforeEach(function()
ranBefore = ranBefore + 1
end)

it('can spy on a property setter', function()
local audio = {
volume = 0,
}
local spy = jestronaut:spyOn(audio, 'volume', 'set')
audio.volume = 100
it('should run beforeEach for each "it" this checks how many (so far)', function()
expect(ranBefore):toEqual(5) -- 5 because this is the 5th "it" in this file
end)

expect(spy):toHaveBeenCalled()
expect(audio.volume):toBe(100)
end)
it('should run beforeAll only once', function()
expect(ranBeforeAll):toEqual(1)
end)

it('can spy on a property getter', function()
local audio = {
volume = 0,
}
local spy = jestronaut:spyOn(audio, 'volume', 'get')
print(audio.volume)
it('can spy on a property setter', function()
local audio = {
volume = 0,
}
local spy = jestronaut:spyOn(audio, 'volume', 'set')
audio.volume = 100

expect(spy):toHaveBeenCalled()
expect(audio.volume):toBe(0)
end)
expect(spy):toHaveBeenCalled()
expect(audio.volume):toBe(100)
end)

it:failing('should be able to expected failures', function()
expect(1 + 1):toEqual(1)
end)
it('can spy on a property getter', function()
local audio = {
volume = 0,
}
local spy = jestronaut:spyOn(audio, 'volume', 'get')
print(audio.volume)

it:skip('should be able to skip tests', function()
expect(1 + 1):toEqual(1)
end)
expect(spy):toHaveBeenCalled()
expect(audio.volume):toBe(0)
end)

it:failing('should be able to expected failures', function()
expect(1 + 1):toEqual(1)
end)

it:each({{1, 1, 2}, {1, 2, 3}, {2, 1, 3}})(
"can loop data (%i, %i = %i)",
function(a, b, expected)
expect(a + b):toBe(expected)
end
)
it:skip('should be able to skip tests', function()
expect(1 + 1):toEqual(1)
end)

it:each({ { 1, 1, 2 }, { 1, 2, 3 }, { 2, 1, 3 } })(
"can loop data (%i, %i = %i)",
function(a, b, expected)
expect(a + b):toBe(expected)
end
)
end)
```
Check out [the Jestronaut tests](https://github.com/luttje/jestronaut/tree/main/tests) for more examples.
Expand Down
41 changes: 30 additions & 11 deletions libs/jestronaut/environment/runner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ local function newTestRunner(runnerOptions)
end)
self.reporter.isVerbose = runnerOptions.verbose

self.runnerOptions = runnerOptions

self.queuedTests = {}
self.processedTests = {}

self.timeout = 5
self.isCompleted = false
self.isStarted = false

self.timeout = runnerOptions.testTimeout or 5000
self.preTestCallback = nil
self.modifyTestResultCallback = nil
self.postTestCallback = nil

self:reset()

return self
end

Expand Down Expand Up @@ -70,12 +70,14 @@ function TEST_RUNNER:queueTest(test)
queuedTest.fn = testFnOrAsyncWrapper
end

table.insert(self.queuedTests, queuedTest)
-- Put in front of the queue, so the tests are run in order
table.insert(self.queuedTests, 1, queuedTest)
end

function TEST_RUNNER:reset()
self.isCompleted = false
self.isStarted = false
self.failedTestCount = 0

for _, test in ipairs(self.processedTests) do
test.status = "starting"
Expand All @@ -101,6 +103,23 @@ function TEST_RUNNER:markFinished(queuedTest, status, err)
end
end

function TEST_RUNNER:handleTestFinished(queuedTest, success, errorMessage)
self:markFinished(queuedTest, success, errorMessage)

if success == false then
self.failedTestCount = self.failedTestCount + 1
end

local bailAfter = self.runnerOptions.bail

if bailAfter ~= nil and self.failedTestCount >= bailAfter then
error(
"Bail after " .. self.failedTestCount .. " failed "
.. (self.failedTestCount == 1 and "test" or "tests") .. " with error: \n" .. errorMessage
)
end
end

function TEST_RUNNER:runTest(queuedTest)
if self.preTestCallback then
self.preTestCallback(queuedTest.test)
Expand Down Expand Up @@ -156,12 +175,12 @@ function TEST_RUNNER:tick()
-- Process queued tests
for i, queuedTest in ipairs(self.queuedTests) do
if (queuedTest.shouldSkip) then
self:markFinished(queuedTest, "skipped")
self:handleTestFinished(queuedTest, nil)
elseif queuedTest.type == "sync" then
-- Run sync tests immediately
local success, errorMessage = self:runTest(queuedTest)

self:markFinished(queuedTest, success, errorMessage)
self:handleTestFinished(queuedTest, success, errorMessage)
elseif queuedTest.type == "async" then
-- Start async test if not started
if queuedTest.status == "starting" then
Expand All @@ -172,7 +191,7 @@ function TEST_RUNNER:tick()

-- Failed even while starting the test
if not success then
self:markFinished(queuedTest, success, errorMessage)
self:handleTestFinished(queuedTest, success, errorMessage)
else
-- Test started, might need further processing
queuedTest.result = queuedTest
Expand All @@ -192,7 +211,7 @@ function TEST_RUNNER:tick()
status, errorMessage = self.modifyTestResultCallback(queuedTest.test, status, errorMessage)
end

self:markFinished(queuedTest, status, errorMessage)
self:handleTestFinished(queuedTest, status, errorMessage)
elseif (queuedTest.asyncWrapper.isDone) then
local errorMessage = queuedTest.asyncWrapper.errorMessage
local success = not errorMessage
Expand All @@ -201,7 +220,7 @@ function TEST_RUNNER:tick()
success, errorMessage = self.modifyTestResultCallback(queuedTest.test, success, errorMessage)
end

self:markFinished(queuedTest, success, errorMessage)
self:handleTestFinished(queuedTest, success, errorMessage)
else
-- Test still in progress
table.insert(remainingTests, queuedTest)
Expand Down
4 changes: 3 additions & 1 deletion libs/jestronaut/environment/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,11 @@ end
local function beforeDescribeOrTest(describeOrTest)
currentDescribeOrTest = describeOrTest

-- TODO: This should not only be local to file, but local to the describe if relevant
local fileLocalState = getTestLocalState(describeOrTest.filePath)

if fileLocalState.beforeAll then
if fileLocalState.beforeAll and not fileLocalState.beforeAllCalled then
fileLocalState.beforeAllCalled = true
fileLocalState.beforeAll()
end

Expand Down
15 changes: 12 additions & 3 deletions tests/readme.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
describe('readme examples', function()
local ranBeforeAll = 0
beforeAll(function()
ranBeforeAll = ranBeforeAll + 1
end)

describe("test organization into suites", function()
it("should confirm basic math", function()
expect(1 + 1):toBe(2)
Expand Down Expand Up @@ -67,12 +72,16 @@ describe('readme examples', function()
end)

local ranBefore = 0
beforeAll(function()
beforeEach(function()
ranBefore = ranBefore + 1
end)

it('should run beforeAll for each "it" this checks how many (so far)', function()
expect(ranBefore):toEqual(4)
it('should run beforeEach for each "it" this checks how many (so far)', function()
expect(ranBefore):toEqual(5) -- 5 because this is the 5th "it" in this file
end)

it('should run beforeAll only once', function()
expect(ranBeforeAll):toEqual(1)
end)

it('can spy on a property setter', function()
Expand Down

0 comments on commit 055616b

Please sign in to comment.