{"id":15414966,"url":"https://github.com/lxsmnsyc/luashould","last_synced_at":"2025-06-14T07:13:49.918Z","repository":{"id":71066955,"uuid":"170815606","full_name":"lxsmnsyc/LuaShould","owner":"lxsmnsyc","description":"BDD-style assertions in Lua","archived":false,"fork":false,"pushed_at":"2019-02-15T08:29:24.000Z","size":19,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-27T05:16:41.032Z","etag":null,"topics":["assertions","bdd","bdd-style","bdd-tests"],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lxsmnsyc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-15T06:48:34.000Z","updated_at":"2019-06-20T22:39:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"eabc8e32-635a-448b-8b9d-6d2e9871c36c","html_url":"https://github.com/lxsmnsyc/LuaShould","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"bdf17167ec9bdb75837a43b6955affa5346c143d"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lxsmnsyc/LuaShould","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxsmnsyc%2FLuaShould","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxsmnsyc%2FLuaShould/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxsmnsyc%2FLuaShould/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxsmnsyc%2FLuaShould/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lxsmnsyc","download_url":"https://codeload.github.com/lxsmnsyc/LuaShould/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxsmnsyc%2FLuaShould/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259775269,"owners_count":22909208,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assertions","bdd","bdd-style","bdd-tests"],"created_at":"2024-10-01T17:05:22.655Z","updated_at":"2025-06-14T07:13:49.902Z","avatar_url":"https://github.com/lxsmnsyc.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LuaShould\nBDD-style assertions in Lua\n\n## Usage\n### Loading the module\n```lua\nlocal this = require \"LuaShould\"\n```\nThe use of the keyword \"this\" allows a verbose understanding on what the context/subject of the assertion is.\n### Simple assertion\n```lua\nthis(5).should.be.a.Number()\n```\nDeclaring a should context allows you to add words for verbosity.\n### Keywords\nKeywords signifies the behavior of the assertion sequence.\n\nKeywords are separated by groups:\n  * Connectors - relays the context, does nothing.\n  * Negators - inverts/negates the next assertion.\n  * Connective Preparation - declares scoped assertion.\n    * Conjunction - tells the next assertions that all of it should pass. If one of the assertion fails, the whole chain fails.\n    * Disjunction - tells the next assertions that one of it should pass. If one of the assertion passes, the whole chain goes on.\n  * Connective Conclusion - concludes the scoped assertion.\n  * Table Access - tells the next assertion to access the value as a table then check for table properties.\n    * Deep Access - tells the next assertion to perform deep access, a recursive form of table access for table values.\n    * Reductors - the AND/OR of table evaluation.\n      * Table Reductors - tells how to strictly check the keys/values.\n      * Parameter Reductors - tells how to check the keys/values based upon the input parameters.\n  * Assertion Operators - performs the assertions.\n  \nLuaShould doesn't really care if the keywords are in upper-case or in lower-case form:\n```lua\nthis(\"Doesn't care about cases\").sHoUlD.bE.tRuThY()\n```\n\nexcept if the keywords are similar to the reserved Lua keywords, you must use the keyword in a different case.\n\n#### Connectors\nConnectors are keywords that adds verbosity to the assertions. They pass the context reference to the next keywords.\nFor example:\n```lua\nthis(\"Hello World\").should.be.of.length(11).and.a.String()\n```\nThe keywords \"should\", \"be\", \"of\", and \"a\" are all Connectors.\nOther connectors includes:\n  * an\n  * been\n  * is\n  * which \n  * the\n  * it\n  * that\n  * also\n  * but\n  \n#### Negators\nNegators inverts the result of the next assertion.\n\nFor example:\n```lua\n-- test.lua\n\nthis(5).should.Not.be.a.Number()\n```\nthrows an error:\n```lua\nLuaShould/test.lua:3: expected the context to not have 'number' type\n```\n\nOther negators includes:\n  * not\n  * no\n  \n##### Notes\n  * Double-negation is handled, so a sequence ```.Not.Not``` is a double-negative thus negates one another.\n  \n#### Connective Preparations \u0026 Conclusions\nConnective Preparation keywords are enables scoped assertions. Scoped assertions are a sequence of keywords that will be evaluated as one once the connective terminates (via Connective Conclusion keywords). \n\nFor example:\n```lua\nthis(\"5\").should.be.either.a.Number.Or.a.String()\n```\nHere, the connective preparation keyword is the word \"either\" and the conclusion keyword is the word \"or\".\nThe sequence first asserts whether or not \"5\" is a Number. If it is, the sequence goes on to the next assertion until the next assertion after the conclusion keyword is evaluated, in this case, the sequence concludes when the keywords String asserts the value.\n\nNegators will negate the result of the concluded scoped assertions if declared before the preparation keyword.\n\nFor example:\n```lua\nthis(\"5\").should.Not.be.both.a.Number.And.a.String()\n```\nSuccesses since the scope \"both.a.Number.And.a.String()\" fails due to connective conjunction.\n\nYou can chain other assertion keywords, as long as the conclusion keyword is not yet met.\n```lua\nthis(5).should.be.both.an.odd.positive.Number.And.finite()\n```\n\nConnective Conjunction Preparations keywords only includes the word \"both\" while the Disjunction includes two words: \"whether\" and \"either\".\n\nConnective Conjunction Conclusion keywords only includes the word \"and\" while the Disjunction includes the word \"or\".\n##### Notes\n  * If the sequence did not include a connective conclusion, the whole sequence won't assert.\n  * If the scoped sequence fails, specially, before the conclusion, and was doomed to not pass their scoped assertion, the error function will display an \"assertion failed!\" message.\n#### Table Access\nTable Access keywords are keywords that only works if the context/subject of assertion is of ```table``` type. These keywords tells the next assertions to be performed on the table keys/values.\n\nFor example:\n```lua\nthis({1, 2, 3, 4, 5}).should.be.a.Number()\n```\nthrows the error\n```lua\nexpected the context to have 'number' type\n```\n\nWhile:\n```lua\nthis({1, 2, 3, 4, 5}).should.contain.some.numbers()\n```\ndoes not, as it checks the table if it contains one or more numbers.\n\nThe table access keywords include:\n  * contains\n  * contain\n  * has\n  * have\n  * with\n  * composed\n  * composes\n  * consisted\n  * consists\n  \n##### Deep Access\nDeep Access allows for recursive table access assertion: if the context table has a table value that did not pass the assertion, it will then be accessed to perform the same assertino to its keys/values.\n```lua\nthis({1, 2, 3, {4, 5, {6, 7, 8}}}).should.deeply.contain.only.numbers()\n```\n##### Reductors\nThere are two kinds of reductors: Table Reductors which tells the conjunction of its assertion, and Parameter Reductors which tells if some or all of the parameters passed to the assertion have to be evaluated.\n\n###### Table Reductors\nKeywords include \"some\" which checks for 1 or more occurences (connective disjunction) and \"only\" which checks if all of the keys/values of the context table passes the assertion (connective conjunction).\n\nBoth of the example sequences below passes the assertion.\n```lua\nthis({123, -124, 0}).should.contain.some.negative()\nthis({-123, -321, -231}).should.contain.only.negative()\n```\n###### Parameter Reductors\nKeywords include \"any\" which passes the assertion if one of the given parameters passes the assertion and \"all\" which requires all of the given parameters to pass the assertion.\n\n```lua\nthis({1, 2, 3, 4, 5}).should.contain.any.of.the.values(9, 6, 3)\nthis({1, 2, 3, 4, 5}).should.contain.all.of.the.values(1, 2, 3)\n```\n##### Notes\n  * if the Table Access keyword is encountered before a Connective Preparation keyword, all of the assertions in that scope will have perform the table access as well.\n  * by default, all sequences have the table reductor as \"some\" and parameter reductor as \"any\"\n  \n#### Assertion Operators\nAssertion operators are keywords that performs the assertions to the context/subject of the sequence. \n\n```lua\nthis(2147483647).should.be.positive()\n```\nthe keyword \"positive\" is an assertion operator which performs a test if the context/subject or the table values (if a Table Access keyword is encountered) is a positive number.\n\nParentheses can be omitted if the assertion operator does not necessarily need a parameter, but to prevent compiler errors, it is recommended to use parentheses, regardless of the parameter requirement, if the test is done without a variable assignment.\n\n```lua\nthis(123456789).should.be.a.Number() -- required parentheses, as the compiler throws an error if the parentheses is omitted.\nlocal test = this(123456789).should.be.a.Number -- does not throw an error\n```\n\nParentheses can also be omitted if the operator is chained\n```lua\nthis(12345).should.be.a.Number.but.should.be.positive()\n```\n\nAssertion operators can also be defined by the user, using the function ```this.define``` which receives the keyword string as the first argument and an assertion function, which is required to throw an error if the evaluation fails. The assertion function receives a table as a first variable which contains the current state of the sequence, which includes:\n  * value = the value/reference of the context\n  * negates = a boolean value which holds the value \"true\" if a negation recently occured in the sequence (scope).\n  * tableAccess = a boolean value which holds the value \"true\" if a Table Access keyword recently occured in the sequence (scope).\n  * deepAccess = a boolean value which holds the value \"true\" if a Deep Access keyword recently occured in the sequence (scope).\n  * tableReductor = a string value which holds the table reduction types: \"some\" and \"only\".\n  * parameterReductor = a string value which holds the parameter reduciton types: \"any\" and \"all\".\n\n```lua\nthis.define(\"divisibleby3\", function (x)\n  assert(x % 3 == 0, \"this is not divisible by 3!\")\nend)\n```\nthen be used as: \n```lua\nthis(21).is.divisibleBy3()\nthis(21).is.DiViSiBlEbY3() -- since the keywords do not really care about their cases.\n```\n\n### A Very Long Example\nMost of the operators and the examples here are included:\n```lua\nlocal this = require \"LuaShould\"\n\nthis(\"Doesn't care about cases\").sHoUlD.bE.tRuThY()\n\nthis({\"table\",\"contains\",\"no\",\"numbers\"}).should.contain.only.Strings()\nthis({1, 2, 3, 4, 5}).should.be.without.Strings()\n\n--[[\n    Booleans\n--]]\nthis(true).should.be.a.Boolean()\nthis(12345).should.Not.be.a.Boolean()\nthis({true, 123, true}).should.contain.some.Booleans()\nthis({true, true, true}).should.contain.only.Booleans()\n\nthis(true).should.be.True()\nthis(123).should.Not.be.True()\nthis({true, 123, true}).should.contain.some.True()\nthis({true, true, true}).should.contain.only.True()\n\nthis(false).should.be.False()\nthis(123).should.Not.be.False()\nthis({false, 123, false}).should.contain.some.False()\nthis({false, false, false}).should.contain.only.False()\n\nthis(123).should.be.Truthy()\nthis(nil).should.Not.be.Truthy()\nthis({1, false, {}}).should.contain.some.Truthy()\nthis({1, \"A\", {}}).should.contain.only.Truthy()\n\nthis(nil).should.be.Falsey()\nthis(123).should.Not.be.Falsey()\nthis({123, false, 123}).should.contain.some.Falsey()\nthis({false, false, false}).should.contain.only.Falsey()\n\nthis(123).should.exist()\nthis(nil).should.Not.exist()\nthis({nil, 123, nil}).should.contain.some.that.exists()\nthis({false, false, false}).should.contain.only.that.exists()\n\nthis(nil).should.be.Nil()\nthis(123).should.Not.Nil()\n-- this({}).should.contain.some.Nil() -- always false, as tables cannot contain nil\nthis({}).should.contain.only.Nil() -- always true if tables are empty\n\nthis(1234).should.be.a.Number()\nthis(\"1234\").should.Not.be.a.Number()\nthis({124, \"142\", 214}).should.contain.some.numbers()\nthis({124, 124, 124}).should.contain.only.numbers()\n\nthis(124).should.be.even()\nthis(123).should.Not.be.even()\nthis({123, 124, 123}).should.contain.some.even()\nthis({124, 124, 124}).should.contain.only.even()\n\n\nthis(123).should.be.odd()\nthis(124).should.Not.be.odd()\nthis({123, 124, 123}).should.contain.some.odd()\nthis({123, 321, 231}).should.contain.only.odd()\n\n\nthis(2147483647).should.be.positive()\nthis(-1).should.Not.be.positive()\nthis({123, -124, 0}).should.contain.some.positive()\nthis({123, 321, 231}).should.contain.only.positive()\n\nthis(-1).should.be.negative()\nthis(2147483647).should.Not.be.negative()\nthis({123, -124, 0}).should.contain.some.negative()\nthis({-123, -321, -231}).should.contain.only.negative()\n\nthis(-1).should.be.finite()\nthis(1/0).should.Not.be.finite()\nthis({-math.huge, -124, 1/0}).should.contain.some.finite()\nthis({-123, -321, -231}).should.contain.only.finite()\n\nthis(math.huge).should.be.infinite()\nthis(1e308).should.Not.be.infinite()\nthis({-math.huge, -124, 1/0}).should.contain.some.infinite()\nthis({1/0, 1e308*2, 2^1024}).should.contain.only.infinite()\n\nthis(0/0).should.be.nan()\nthis(1e309).should.Not.be.nan()\nthis({math.huge, 0/0, math.huge}).should.contain.some.nan()\nthis({math.huge/math.huge, 0/0, math.huge*0}).should.contain.only.nan()\n\nthis(\"123\").should.be.numeric()\nthis(false).should.Not.be.numeric()\nthis({print, \"0xDEAD\", false}).should.contain.some.numeric()\nthis({\"123\", \"0xDEAD\", \"1e5\"}).should.contain.only.numeric()\n\nthis(\"5\").should.be.either.Not.a.Number.Or.a.String()\nthis(5).should.be.both.an.odd.positive.Number.And.finite()\nthis({1, 2, 3, 4, 5}).should.contain.some.numbers()\n\nthis({1, 2, 3, {4, 5, {6, 7, 8}}}).should.deeply.contain.only.numbers()\n\nthis({1, 2, 3, 4, 5}).should.contain.any.of.the.values(9, 6, 3)\nthis({1, 2, 3, 4, 5}).should.contain.all.of.the.values(1, 2, 3)\n\nthis.define(\"divisibleby3\", function (x)\n  assert(x.value % 3 == 0, \"this is not divisible by 3!\")\nend)\n\nthis(21).is.divisibleBy3()\n```\n\n### Author Notes\nThe library is still in the development. Operators and keywords would be extended in the future.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flxsmnsyc%2Fluashould","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flxsmnsyc%2Fluashould","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flxsmnsyc%2Fluashould/lists"}