PageSourceSearch

https://kotest.io/assets/js/6ad2f7f2.3df52bf7.js

js kotest.io collected 2026-10-03 21:53:08 UTC 16,617 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkkotestdocs=globalThis.webpackChunkkotestdocs||[]).push([[3791],{12950(e,t,s){s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>c,default:()=>h,frontMatter:()=>r,metadata:()=>n,toc:()=>o});const n=JSON.parse('{"id":"framework/lifecycle_hooks","title":"Lifecycle hooks","description":"It is extremely common in tests to want to perform some action before and after a test, or before and after all tests in the same file.","source":"@site/versioned_docs/version-5.3.x/framework/lifecycle_hooks.md","sourceDirName":"framework","slug":"/framework/lifecycle-hooks.html","permalink":"/docs/5.3.x/framework/lifecycle-hooks.html","draft":false,"unlisted":false,"editUrl":"https://github.com/kotest/kotest/blob/master/documentation/versioned_docs/version-5.3.x/framework/lifecycle_hooks.md","tags":[],"version":"5.3.x","frontMatter":{"id":"lifecycle_hooks","title":"Lifecycle hooks","slug":"lifecycle-hooks.html","sidebar_label":"Lifecycle hooks"},"sidebar":"framework","previous":{"title":"Isolation Modes","permalink":"/docs/5.3.x/framework/isolation-mode.html"},"next":{"title":"Introduction","permalink":"/docs/5.3.x/framework/extensions/extensions-introduction.html"}}');var i=s(74848),a=s(28453);const r={id:"lifecycle_hooks",title:"Lifecycle hooks",slug:"lifecycle-hooks.html",sidebar_label:"Lifecycle hooks"},c=void 0,l={},o=[{value:"DSL Methods",id:"dsl-methods",level:4},{value:"DSL methods with functions",id:"dsl-methods-with-functions",level:4},{value:"Overriding callback functions in a Spec",id:"overriding-callback-functions-in-a-spec",level:4}];function d(e){const t={a:"a",br:"br",code:"code",em:"em",h4:"h4",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,a.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(t.p,{children:["It is extremely common in tests to want to perform some action before and after a test, or before and after all tests in the same file.\nIt is in these ",(0,i.jsx)(t.em,{children:"lifecycle hooks"})," that you would perform any setup/teardown logic required for a test."]}),"\n",(0,i.jsxs)(t.p,{children:["Kotest provides a rich assortment of hooks that can be defined directly inside a spec.\nFor more advanced cases, such as writing distributable plugins or re-usable hooks, one can use ",(0,i.jsx)(t.a,{href:"/docs/5.3.x/framework/extensions/extensions-introduction.html",children:"extensions"}),"."]}),"\n",(0,i.jsx)(t.p,{children:"At the end of this section is a list of the available hooks and when they are executed."}),"\n",(0,i.jsx)(t.p,{children:"There are several ways to use hooks in Kotest:"}),"\n",(0,i.jsx)(t.h4,{id:"dsl-methods",children:"DSL Methods"}),"\n",(0,i.jsxs)(t.p,{children:["The first and simplest, is to use the DSL methods available inside a Spec which create and register a ",(0,i.jsx)(t.code,{children:"TestListener"})," for you. For example, we can invoke ",(0,i.jsx)(t.code,{children:"beforeTest"})," or ",(0,i.jsx)(t.code,{children:"afterTest"})," (and others) directly alongside our tests."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-kotlin",children:'class TestSpec : WordSpec({\n  beforeTest {\n    println("Starting a test $it")\n  }\n  afterTest { (test, result) ->\n    println("Finished spec with result $result")\n  }\n  "this test" should {\n    "be alive" {\n      println("Johnny5 is alive!")\n    }\n  }\n})\n'})}),"\n",(0,i.jsxs)(t.p,{children:["Behind the scenes, these DSL methods will create an instance of ",(0,i.jsx)(t.code,{children:"TestListener"}),", overriding the appropriate functions, and ensuring that this test listener is registered to run."]}),"\n",(0,i.jsxs)(t.p,{children:["You can use ",(0,i.jsx)(t.code,{children:"afterProject"})," as a DSL method which will create an instance of ",(0,i.jsx)(t.code,{children:"ProjectListener"}),", but there is no ",(0,i.jsx)(t.code,{children:"beforeProject"})," because by the time the framework is at this stage of detecting a spec, the project has already started!"]}),"\n",(0,i.jsx)(t.h4,{id:"dsl-methods-with-functions",children:"DSL methods with functions"}),"\n",(0,i.jsxs)(t.p,{children:["Since these DSL methods accept functions, we can pull out logic to a function and re-use it in several places. The ",(0,i.jsx)(t.code,{children:"BeforeTest"})," type used on the function definition is an alias\nto ",(0,i.jsx)(t.code,{children:"suspend (TestCase) -> Unit"})," to keep things simple. There are aliases for the types of each of the callbacks."]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-kotlin",children:'val startTest: BeforeTest = {\n   println("Starting a test $it")\n}\n\nclass TestSpec : WordSpec({\n\n   // used once\n   beforeTest(startTest)\n\n   "this test" should {\n      "be alive" {\n         println("Johnny5 is alive!")\n      }\n   }\n})\n\nclass OtherSpec : WordSpec({\n\n   // used twice\n   beforeTest(startTest)\n\n   "this test" should {\n      "fail" {\n         fail("boom")\n      }\n   }\n})\n'})}),"\n",(0,i.jsx)(t.h4,{id:"overriding-callback-functions-in-a-spec",children:"Overriding c
1allback functions in a Spec"}),"\n",(0,i.jsx)(t.p,{children:"The second, related, method is to override the callback functions in the Spec. This is essentially just a variation on the first method."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-kotlin",children:'class TestSpec : WordSpec() {\n    override fun beforeTest(testCase: TestCase) {\n        println("Starting a test $testCase")\n    }\n\n    init {\n        "this test" should {\n            "be alive" {\n                println("Johnny5 is alive!")\n            }\n        }\n    }\n}\n'})}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(t.table,{children:[(0,i.jsx)(t.thead,{children:(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.th,{children:"Callback"}),(0,i.jsx)(t.th,{children:"Description"})]})}),(0,i.jsxs)(t.tbody,{children:[(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"beforeContainer"}),(0,i.jsxs)(t.td,{children:["Invoked directly before each test with type ",(0,i.jsx)(t.code,{children:"TestType.Container"})," is executed, with the ",(0,i.jsx)(t.code,{children:"TestCase"})," instance as a parameter. If the test is marked as ignored / disabled / inactive, then this callback won't be invoked."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"afterContainer"}),(0,i.jsxs)(t.td,{children:["Invoked immediately after a ",(0,i.jsx)(t.code,{children:"TestCase"})," with type ",(0,i.jsx)(t.code,{children:"TestType.Container"})," has finished, with the ",(0,i.jsx)(t.code,{children:"TestResult"})," of that test. If a test case was skipped (ignored / disabled / inactive) then this callback will not be invoked for that particular test case.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"The callback will execute even if the test fails."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"beforeEach"}),(0,i.jsxs)(t.td,{children:["Invoked directly before each test with type ",(0,i.jsx)(t.code,{children:"TestType.Test"})," is executed, with the ",(0,i.jsx)(t.code,{children:"TestCase"})," instance as a parameter. If the test is marked as ignored / disabled / inactive, then this callback won't be invoked."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"afterEach"}),(0,i.jsxs)(t.td,{children:["Invoked immediately after a ",(0,i.jsx)(t.code,{children:"TestCase"})," with type ",(0,i.jsx)(t.code,{children:"TestType.Test"})," has finished, with the ",(0,i.jsx)(t.code,{children:"TestResult"})," of that test. If a test case was skipped (ignored / disabled / inactive) then this callback will not be invoked for that particular test case.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"The callback will execute even if the test fails."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"beforeAny"}),(0,i.jsxs)(t.td,{children:["Invoked directly before each test with any ",(0,i.jsx)(t.code,{children:"TestType"})," is executed, with the ",(0,i.jsx)(t.code,{children:"TestCase"})," instance as a parameter. If the test is marked as ignored / disabled / inactive, then this callback won't be invoked."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"afterAny"}),(0,i.jsxs)(t.td,{children:["Invoked immediately after a ",(0,i.jsx)(t.code,{children:"TestCase"})," with any ",(0,i.jsx)(t.code,{children:"TestType"})," has finished, with the ",(0,i.jsx)(t.code,{children:"TestResult"})," of that test. If a test case was skipped (ignored / disabled / inactive) then this callback will not be invoked for that particular test case.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"The callback will execute even if the test fails."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"beforeTest"}),(0,i.jsxs)(t.td,{children:["Invoked directly before each test is executed with the ",(0,i.jsx)(t.code,{children:"TestCase"})," instance as a parameter. If the test is marked as ignored / disabled / inactive, then this callback won't be invoked.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"This callback has the same behavior as ",(0,i.jsx)(t.code,{children:"beforeAny"}),"."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"afterTest"}),(0,i.jsxs)(t.td,{children:["Invoked immediately after a ",(0,i.jsx)(t.code,{children:"TestCase"})," has finished with the ",(0,i.jsx)(t.code,{children:"TestResult"})," of that test. If a test case was skipped (ignored / disabled / inactive) then this callback will not be invoked for that particular test case.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"The callback will execute even if the test fails.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"This callback has the same behavior as ",(0,i.jsx)(t.code,{children:"afterAny"}),"."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"beforeSpec"}),(0,i.jsxs)(t.td,{children:["Invoked after the Engine instantiates a spec to be used as part of a test execution.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"The callback is provided with the ",(0,i.jsx)(t.code,{children:"Spec"})," instance that the test will be executed under.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"If a spec is instantiated multiple times - for example, if ",(0,i.jsx)(t.code,{children:"InstancePerTest"})," or ",(0,i.jsx)(t.code,{children:"InstancePerLeaf"})," isolation modes are used, then this callback will be invoked for each instance created, just before the first test (or only test) is executed for that spec.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"This callback should be used if you need to perform setup each time a new spec instance is created.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"If you simply need to perform setup once per class file, then use prepareSpec. This callback runs before any ",(0,i.jsx)(t.code,{children:"beforeTest"})," functions are invoked.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{})," When running in the default ",(0,i.jsx)(t.code,{children:"SingleInstance"})," isolation mode, then this callback and ",(0,i.jsx)(t.code,{children:"prepareSpec"})," are functionally the same since all tests will run in the same spec instance."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"afterSpec"}),(0,i.jsxs)(t.td,{children:["Is invoked after the ",(0,i.jsx)(t.code,{children:"TestCase"}),"s that are part of a particular spec instance have completed.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"If a spec is instantiated multiple times - for example, if ",(0,i.jsx)(t.code,{children:"InstancePerTest"})," or ",(0,i.jsx)(t.code,{children:"InstancePerLeaf"})," isolation modes are used, then this callback will be invoked for each instantiated spec, after the tests that are applicable to that spec instance have returned.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"This callback should be used if you need to perform cleanup after each individual spec instance. If you need to perform cleanup once per class file, then use ",(0,i.jsx)(t.code,{children:"finalizeSpec."}),(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"This callback runs after any ",(0,i.jsx)(t.code,{children:"afterTest"})," callbacks have been invoked.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"When running in the default ",(0,i.jsx)(t.code,{children:"SingleInstance"})," isolation mode, then this callback and ",(0,i.jsx)(t.code,{children:"finalizeSpec"})," are functionally the same since all tests will run in the same spec instance.",(0,i.jsx)(t.br,{}),"In case there is any exception in ",(0,i.jsx)(t.code,{children:"beforeSpec"}),", ",(0,i.jsx)(t.code,{children:"afterSpec"})," will be skipped"]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"prepareSpec"}),(0,i.jsxs)(t.td,{children:["Called once per spec, when the engine is preparing to execute the tests for that spec. The ",(0,i.jsx)(t.code,{children:"KClass"})," instance of the spec is provided as a parameter.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"Regardless of how many times the spec is instantiated, for example, if ",(0,i.jsx)(t.code,{children:"InstancePerTest"})," or ",(0,i.jsx)(t.code,{children:"InstancePerLeaf"})," isolation modes are used, this callback will only be invoked once. If there are no active tests in a spec, then this callback will still be invoked.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"When running in the default ",(0,i.jsx)(t.code,{children:"SingleInstance"})," isolation mode, then this callback and ",(0,i.jsx)(t.code,{children:"beforeSpec"})," are functionally the same since all tests will run in the same spec instance."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"finalizeSpec"}),(0,i.jsxs)(t.td,{children:["Called once per ",(0,i.jsx)(t.code,{children:"Spec"}),", after all tests have completed for that spec.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"Regardless of how many times the spec is instantiated, for example, if ",(0,i.jsx)(t.code,{children:"InstancePerTest"})," or ",(0,i.jsx)(t.code,{children:"InstancePerLeaf"})," isolation modes are used, this callback will only be invoked once.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"The results parameter contains every ",(0,i.jsx)(t.code,{children:"TestCase"}),", along with the result of that test, including tests that were ignored (which will have a ",(0,i.jsx)(t.code,{children:"TestResult"})," that has ",(0,i.jsx)(t.code,{children:"TestStatus.Ignored"}),").",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"When running in the default ",(0,i.jsx)(t.code,{children:"SingleInstance"})," isolation mode, then this callback and ",(0,i.jsx)(t.code,{children:"afterSpec"})," are functionally the same since all tests will run in the same spec instance."]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"beforeInvocation"}),(0,i.jsxs)(t.td,{children:["Invoked before each 'run' of a test, with a flag indicating the iteration number. This callback is useful if you have set a test to have multiple invocations via config and want to do some setup / teardown between runs.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"If you are running a test with the default single invocation then this callback is effectively the same as ",(0,i.jsx)(t.code,{children:"beforeTest"}),".",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),(0,i.jsxs)(t.em,{children:["Note: If you have set multiple invocations ",(0,i.jsx)(t.em,{children:"and"})," multiple threads, then these callbacks will be invoked concurrently."]})]})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{children:"afterInvocation"}),(0,i.jsxs)(t.td,{children:["Invoked after each 'run' of a test, with a flag indicating the iteration number. This callback is useful if you have set a test to have multiple invocations via config and want to do some setup / teardown between runs.",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),"If you are running a test with the default single invocation then this callback is effectively the same as ",(0,i.jsx)(t.code,{children:"afterTest"}),".",(0,i.jsx)(t.br,{}),(0,i.jsx)(t.br,{}),(0,i.jsxs)(t.em,{children:["Note: If you have set multiple invocations ",(0,i.jsx)(t.em,{children:"and"})," multiple threads, then these callbacks will be invoked concurrently."]})]})]})]})]})]})}function h(e={}){const{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},28453(e,t,s){s.d(t,{R:()=>r,x:()=>c});var n=s(96540);const i={},a=n.createContext(i);function r(e){const t=n.useContext(a);return n.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),n.createElement(a.Provider,{value:t},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.