diff --git a/tutorials/Web/README.md b/tutorials/Web/README.md
index b0795d75c9..f9ac621ab5 100644
--- a/tutorials/Web/README.md
+++ b/tutorials/Web/README.md
@@ -10,3 +10,5 @@
[Handing Events](Events_Handling/README.md) - a short overview of Events handling with compose web
[Style DSL](Style_Dsl/README.md) - about styling the composable components in web
+
+[Using test-utils](Using_Test_Utils/README.md) - how to use test-utils for DOM DSL unit testing
diff --git a/tutorials/Web/Using_Test_Utils/README.md b/tutorials/Web/Using_Test_Utils/README.md
new file mode 100644
index 0000000000..058b8218fc
--- /dev/null
+++ b/tutorials/Web/Using_Test_Utils/README.md
@@ -0,0 +1,99 @@
+# Using test-utils for DOM DSL unit testing
+
+### Dependencies
+
+It's necessary to add `compose.web.testUtils` to jsTest dependencies:
+
+``` kotlin
+sourceSets {
+ val jsMain by getting {
+ dependencies {
+ implementation(compose.web.core)
+ implementation(compose.runtime)
+ //....
+ }
+ }
+ val jsTest by getting {
+ implementation(kotlin("test-js"))
+ implementation(compose.web.testUtils)
+ //...
+ }
+}
+```
+
+
+### Example
+
+``` kotlin
+// This is a function that we want to test
+@Composable
+fun TestButton(text: String, onButtonClick: () -> Unit) {
+ Button(attrs = {
+ onClick { onButtonClick() }
+ }) {
+ Text(text)
+ }
+}
+```
+
+Let's add a test to ensure that button has correct text, and it's onClick works properly.
+``` kotlin
+import org.jetbrains.compose.web.testutils.ComposeWebExperimentalTestsApi
+import org.jetbrains.compose.web.testutils.runTest
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import org.w3c.dom.HTMLButtonElement
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+@OptIn(ComposeWebExperimentalTestsApi::class)
+class TestsForButton {
+
+ @Test
+ fun testButton() = runTest {
+ var counter by mutableStateOf(1)
+
+ composition {
+ TestButton(text = "$counter") {
+ counter++
+ }
+ }
+
+ assertEquals("", root.innerHTML)
+
+ (root.firstChild!! as HTMLButtonElement).click()
+ waitForRecompositionComplete()
+ assertEquals("", root.innerHTML)
+
+ counter = 10
+ waitForRecompositionComplete()
+ assertEquals("", root.innerHTML)
+ }
+}
+```
+
+### Let's break it down:
+
+### `runTest { ... }`
+Provides the TestScope with useful functions to configure the test.
+
+### `composition { ... }`
+Takes a @Composable block with a content that we want to test.
+It will automatically build and mount DOM into `root` element.
+
+### `root`
+It's not supposed to be used for elements manipulation.
+It's mostly useful to make assertions on the html content (e.g. `root.innerHtml`)
+
+### `nextChild() and currentChild()`
+Under the hood `nextChild()` iterates over `root` children, providing convenient access to them.
+
+`currentChild()` doesn't move the iterator and returns the same element every time until `nextChild()` called.
+
+### `waitForRecompositionComplete()`
+It suspends until recomposition completes. It's useful when state changes, and we want to test that content updates as well. `waitForRecompositionComplete` needs to be called after state change and before assertions.
+
+### `waitForChanges(id: String)`
+It suspends until any change occur in the element with `id`.
+It's also useful to ensure that state changes make corresponding updates to the content.