Using the 'after' hook in TestCafe to Cleanup Data
Tags: javascript, testing, typescript, tutorial
Published: 2020-02-15 15:47:05

Here's a quick tutorial on how to use the `after` hook on a TestCafe test. 

You'll want to use this hook if your test is creating test data in your environment and you don't want to keep that data around. For example, that test user that you've just created while testing registration or that test product that you just added to your cart or that fake comment you left on a blog post, all of these could be deleted after the test is finished.

Take a look at this test:

```js
import { Selector } from 'testcafe';

fixture('Creating Blog Posts').page('/posts/newPost');

test('should be able to create a new blog post', async (t: TestController) => {
  await BlogModel.addPost('This is a Test Post');
  await t.click(Selector('.submit-button'));
});

```
We're first going to add a new blog post named 'This is a Test Post'. In this fictional app the user is redirected to the page `/posts` which lists all available posts. We'll want to click our test post link to make sure this new post is viewable:

```js
import { Selector } from 'testcafe';

fixture('Creating Blog Posts').page('/posts/newPost');

test('should be able to create a new blog post', async (t: TestController) => {
  await BlogModel.addPost('This is a Test Post', `Here's some post content`);
  await t.click(Selector('.submit-button'));
  await t.click(Selector('.post').withText('This is a Test Post'));

  await t.expect(Selector('h1.postName').withText('This is a Test Post').exists).ok()
});

```

Let's assume each post can be viewed at a URL like `/post/id-of-post`. 

We've added the post, opened the post and verified that a user can see it. Everything's passing 😀. Now what I want to do is delete this post. 

This part might be a little tricky. You could choose to use the UI to delete the post. This could work if, for example, you have a "Delete Post" button in the UI on all posts that your user created. Let's imagine that we don't have that option. What we do have is a rest API for working with posts (adding, updating, and deleting). The UI uses this API for adding a post, and we can use the API for removing posts too. All I need is the post's ID. 

Each post has the ID in the URL. To grab it we'll use the `ClientFunction` method that is provided by TestCafe. This method allows us to interact with the browser from inside a test. That means we have access to the `window` object, which in turn means we can grab the `window.location`.

```js
import { ClientFunction, Selector } from 'testcafe';

const getWindowLocation = ClientFunctio(() => window.location);

fixture('Creating Blog Posts').page('/posts/newPost');

test('should be able to create a new blog post', async (t: TestController) => {
  await BlogModel.addPost('This is a Test Post', `Here's some post content`);
  await t.click(Selector('.submit-button'));
  await t.click(Selector('.post').withText('This is a Test Post'));
  
  await t.expect(Selector('h1.postName').withText('This is a Test Post').exists).ok()

  const location = await getWindowLocation();
});

```

To get the post's ID from the URL we can split the `pathname` and grab everything after `/post/`:

```js
import { ClientFunction, Selector } from 'testcafe';

const getWindowLocation = ClientFunction(() => window.location);

fixture('Creating Blog Posts').page('/posts/newPost');

test('should be able to create a new blog post', async (t: TestController) => {
  await BlogModel.addPost('This is a Test Post', `Here's some post content`);
  await t.click(Selector('.submit-button'));
  await t.click(Selector('.post').withText('This is a Test Post'));
  
  await t.expect(Selector('h1.postName').withText('This is a Test Post').exists).ok()

  const location = await getWindowLocation();
  t.ctx.postId = location.pathname.split('/post/')[0];
});

```

We assign the value to `t.ctx.postId` because we want to use that value in an `after` hook. 

> `ctx` is available on the `TestController` and used to share data between test code and hooks.

Now we can add an `after` hook to delete that post:

```js
import { ClientFunction, Selector } from 'testcafe';

const getWindowLocation = ClientFunction(() => window.location);

fixture('Creating Blog Posts').page('/posts/newPost');

test('should be able to create a new blog post', async (t: TestController) => {
  await BlogModel.addPost('This is a Test Post', `Here's some post content`);
  await t.click(Selector('.submit-button'));
  await t.click(Selector('.post').withText('This is a Test Post'));
  
  await t.expect(Selector('h1.postName').withText('This is a Test Post').exists).ok()

  const location = await getWindowLocation();
  t.ctx.postId = location.pathname.split('/post/')[0];
}).after(async (t: TestController) => {
  // assume blogService handles API calls
  await blogService.deletePost(t.ctx.postId);
});

```

Now that we are using the `after` hook, we're able to remove the test post. Our environment won't fill up with tons of posts! 

Hopefully, this helps you out if you're running automated tests with TestCafe. 

Check out my other TestCafe posts here on dev.to, and if you want to reeaaalllly get into TestCafe, [watch my course](https://pluralsight.pxf.io/jjMvP) on Pluralsight.

Follow me on [twitter](https://twitter.com/mwq27) 😀