From f812d24b9eb3a7470796d77dcd173989cd622681 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Fri, 15 Dec 2023 16:21:08 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5=20Remove=20the=20old=20testing=20g?= =?UTF-8?q?uide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- www/docs/structure.json | 1 - www/docs/testing.mdx | 424 ---------------------------------------- 2 files changed, 425 deletions(-) delete mode 100644 www/docs/testing.mdx diff --git a/www/docs/structure.json b/www/docs/structure.json index 4bc3917ae..2e39843d1 100644 --- a/www/docs/structure.json +++ b/www/docs/structure.json @@ -13,7 +13,6 @@ ], "Advanced": [ ["scope.mdx", "Scope"], - ["testing.mdx", "Testing"], ["processes.mdx", "Processes"] ] } diff --git a/www/docs/testing.mdx b/www/docs/testing.mdx deleted file mode 100644 index 928d3d368..000000000 --- a/www/docs/testing.mdx +++ /dev/null @@ -1,424 +0,0 @@ ->⚠️ These docs have not been updated from version 2 of Effection, and do not -> apply to version 3. The information you find here may be of use, but may -> also be outdated or misleading. - -Effection not only simplifies the process of writing tests, but by -managing all the timing complexity of your setup and teardown, it can -often make new kinds of tests possible that beforehand may have seemed -unachievable. - -Currently, both [Jest][jest] and [Mocha][mocha] are supported out of the box, but -because of the way Effection works, and because of the nature of task -you have to do while testing, it's easy to integrate it with -any test framework you like. - -## Lifecycle - -The reason Effection can so effectively power your tests is because the -life-cycle of a test mirrors that of an Effection Task almost perfectly. - -Consider that almost every test framework executes the following sequence of -operations when running a test: - -1. setup() -2. run() -3. teardown() - -While there are certainly different ways of expressing this -syntactically, it turns out that as a fundamental testing pattern, it -is nearly universal. Notice that, like tests, the cleanup of an -effection operation is intrinsic. We can leverage this fact to -associate an effection task with each test. Then, we run any -operation that is part of the test as a child of that task. After it -is finished, a test's task is halted , thereby halting any sub-tasks -that were running as a part of it. - -This means that any resources being used by the testcase such as servers, -database connections, file handles, etc... can be automatically released without -writing any cleanup logic explicitly into the test itself. In essense, your -tests are able to completely eliminate `teardown` altogether and become: - -1. setup() -2. run() - -## Jest and Mocha - -Effection provides packages for seamless integration between [Jest][jest] and -[Mocha][mocha]. - -To get started, install from NPM - -### Writing Tests - -Let's say we have a test case written without the benefit of effection that -looks like this: - -``` javascript -describe("a server", () => { - let server: Server; - beforeEach(async () => { - server = await startServer({ port: 3500 }); - }); - - it('can be pinged', async () => { - let response = await fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); - - afterEach(async () => { - await server.close(); - }); -}); -``` -:::note Cleanup - -It is critical that we shutdown the server after each test. Otherwise, node -will never exit without a hard stop because it's still bound to port -`3500`, and worse, every subsequent time we try and run our tests they will fail -because port `3500` is not available! -::: - -To re-write this test case using effection, there are two superficial -differences from the vanilla version of the framework. - -1. You `import` all of your test syntax from the effection package, and not -from the global namespace. -2. You use [generator functions instead of async functions][replace-async] to -express all of your test operations. - -Once we make those changes, our test case now looks like this. - -> TODO: tab item - -``` javascript -import { beforeEach, afterEach, it } from '@effection/jest'; - -describe("a server", () => { - let server: Server; - beforeEach(function*() { - server = yield startServer({ port: 3500 }); - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); - - afterEach(function*() { - yield server.close(); - }); -}); -``` - -> TODO: tab item - -``` javascript -import { beforeEach, afterEach, it } from '@effection/mocha'; - -describe("a server", () => { - let server: Server; - beforeEach(function*() { - server = yield startServer({ port: 3500 }); - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); - - afterEach(function*() { - yield server.close(); - }); -}); -``` - -But so far, we've only traded one syntax for another. Now however, we can -begin to leverage the power of Effection to make our test cases not only more -concise, but also more flexible. To do this, we use the fact that each test -case gets its own task that is automatically halted for us after it runs. - -So if we re-cast our server as a [resource][] that is running as a -child of our test-scoped task, then when the test task goes away, so -will our server. - - -> TODO: tab item - -``` javascript -import { ensure } from 'effection'; -import { beforeEach, it } from '@effection/jest'; - -describe("a server", () => { - beforeEach(function*() { - yield { - name: 'ping server', - *init() { - let server = yield startServer({ port: 3500 }); - yield ensure(() => server.close()) - } - } - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); -}); -``` - -> TODO: tab item - -``` javascript -import { ensure } from 'effection'; -import { beforeEach, it } from '@effection/mocha'; - -describe("a server", () => { - beforeEach(function*() { - yield { - name: 'ping server', - *init() { - let server = yield startServer({ port: 3500 }); - yield ensure(() => server.close()) - } - } - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); -}); -``` - -We don't have to write an `afterEach()` _at all_ anymore, because the resource -knows how to tear itself down no matter where it ends up running. - -This has the added benefit of making your tests more refactorable. For example, -let's say that we decided that since our server is read-only, we can actually -start it once before all of the tests run, so we move it to a _suite_ level -hook. Before, we would have had to remember to convert our teardown hook as -well, but with Effection, we can just move our resource to its new location. - -> TODO: tab item - -``` javascript -import { ensure } from 'effection'; -import { beforeAll, it } from '@effection/jest'; - -describe("a server", () => { - beforeAll(function*() { - yield { - name: 'ping server', - *init() { - let server = yield startServer({ port: 3500 }); - yield ensure(() => server.close()) - } - } - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); -}); -``` - -> TODO: tab item - -``` javascript -import { ensure } from 'effection'; -import { before, it } from '@effection/mocha'; - -describe("a server", () => { - before(function*() { - yield { - name: 'ping server', - *init() { - let server = yield startServer({ port: 3500 }); - nyield ensure(() => server.close()) - } - } - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); -}); -``` - -It is common to extract such resources into re-usable functions that can be -embedded anywhere into your test suite, and you never have to worry about them -being torn down. For example, we could define our server resource in a single -place: - -``` javascript -//server.js -import { ensure } from 'effection'; - -export function createServer(options) { - let { port = 3500, name = 'ping server' } = options; - return { - name, - *init() { - let server = yield startServer({ port }); - yield ensure(() => server.close()); - } - } -} -``` - -Then we can use it easily in any test case: - -> TODO: tab item - -``` javascript -import { beforeAll, it } from '@effection/jest'; -import { createServer } from './server'; - -describe("a server", () => { - beforeAll(function*() { - yield createServer({ port: 3500 }); - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); -}); -`` - -> TODO: tab item - -``` javascript -import { before, it } from '@effection/mocha'; -import { createServer } from './server'; - -describe("a server", () => { - before(function*() { - yield createServer({ port: 3500 }); - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - }); -}); -``` - -### Test Scope - -As hinted at above, there are two separate levels of task in -your tests: _suite-scoped_, and _test-scoped_. Effection creates one -task that has the same lifetime as the beginning and end of your test -suite. Any task spawned within it can potentially last across multiple -test runs. By the same token, the _test-scoped_ task is created before -and halted after every single test. Any tasks spawned within it will -be halted immediately after the test is finished. For example: - - -> TODO: tab item - -``` javascript -import { beforeAll, beforeEach, it } from '@effection/jest'; -import { createServer } from './server'; - -describe("a server", () => { - beforeAll(function*() { - // started once before both `it` blocks - // stopped once after both `it` blocks - yield createServer({ port: 3500 }); - }); - - beforeEach(function*() { - // started before every `it` block - // stopped after every `it` block, - yield createServer({ port: 3501 }); - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - - let response = yield fetch('http://localhost:3501/ping'); - expect(response.ok).toBe(true); - }); - - it('can be ponged', function*() { - let response = yield fetch('http://localhost:3500/pong'); - expect(response.ok).toBe(true); - - let response = yield fetch('http://localhost:3501/pong'); - expect(response.ok).toBe(true); - }); -}); -``` - -> TODO: tab item - -``` javascript -import { before, beforeEach, it } from '@effection/mocha'; -import { createServer } from './server'; - -describe("a server", () => { - before(function*() { - // started once before both `it` blocks - // stopped once after both `it` blocks - yield createServer({ port: 3500 }); - }); - - beforeEach(function*() { - // started before every `it` block - // stopped after every `it` block, - yield createServer({ port: 3501 }); - }); - - it('can be pinged', function*() { - let response = yield fetch('http://localhost:3500/ping'); - expect(response.ok).toBe(true); - - let response = yield fetch('http://localhost:3501/ping'); - expect(response.ok).toBe(true); - }); - - it('can be ponged', function*() { - let response = yield fetch('http://localhost:3500/pong'); - expect(response.ok).toBe(true); - - let response = yield fetch('http://localhost:3501/pong'); - expect(response.ok).toBe(true); - }); -}); -``` - -### Caveats - -:::note Jest - -There is currently a [critical bug in -Jest](https://github.com/facebook/jest/issues/12259) that causes -teardown hooks to be completely ignored if you hit `CTRL-C` while your -tests are running. This is true for any Jest test whether you're using -Effection or not, but you must be careful about interrupting your -tests from the command line while they are running as it can have -unknown consequences. - -If you'd like to see this fixed, please [go to the -issue](https://github.com/facebook/jest/issues/12259) and leave a -comment. - -::: - -## Other Frameworks - -Have a favorite testing tool that you don't see listed here that you -think could benefit from a first class Effection integration? Feel -free to [create an issue for -it](https://github.com/thefrontside/effection/issues/new) or [drop -into discord][discord] and let us know! - -[mocha]: https://mochajs.org -[jest]: https://jestjs.io -[resource]: /docs/guides/resources -[replace-async]: /docs/guides/introduction#replacing-asyncawait -[discord]: https://discord.gg/r6AvtnU