The pattern to make your FE tests 10x better.
Tags: testing, vitest, vue, frontend
Published: 2023-04-07 09:04:10

## Encapsulating Test Expects and Actions in Separate Functions

Writing test cases for your code can be a tedious task, especially when you have to test multiple scenarios with various inputs and outputs. However, there are ways to make your test cases more organized, easier to read, and maintainable.

One of the approaches is to encapsulate your test expects and actions in separate functions. This approach can improve your test code quality, reduce code duplication, and promote reusability.

Here's how you can do it:

## Encapsulating Test Expects

When you write test cases, you need to assert that certain conditions are met or certain values are returned. Instead of scattering your expect statements throughout your test case, you can encapsulate them in a separate function.

For example:

```javascript
const initExpects = (wrapper) => {
  const expectMessage = (message) => {
    expect(wrapper.find('.message').text()).toBe(message)
  }

  const expectButtonVisible = (buttonName) => {
    expect(wrapper.find(`button.${buttonName}`).exists()).toBe(true)
  }

  // ...

  return {
    expectMessage,
    expectButtonVisible,
    // ...
  }
}
```
In the above code, initExpects is a function that takes a wrapper object and returns an object that contains various expect functions. These functions can be reused in other test cases as well.

It's worth noting that the `expects object` can include expectations not only for the UI but also for the store, since integration testing allows us to test the interaction between different components and modules in the application.

## Encapsulating Test Actions

Similarly, you can encapsulate your test actions in a separate function as well. Test actions are the functions that simulate user interactions on your component or application, such as clicking a button or typing into an input field.

Here's an example:

```javascript
const initActions = (wrapper) => {
  const clickButton = (buttonName) => {
    wrapper.find(`button.${buttonName}`).trigger('click')
  }

  const setInputValue = (inputName, value) => {
    const input = wrapper.find(`input[name="${inputName}"]`)
    input.setValue(value)
  }

  // ...

  return {
    clickButton,
    setInputValue,
    // ...
  }
}
```
In the above code, initActions is a function that takes a wrapper object and returns an object that contains various action functions. These functions can be used to simulate user interactions in your test cases.

## Putting it All Together

Here's how you can use the initExpects and initActions functions in your test cases:

```javascript
describe('My Component', () => {
  let wrapper
  let actions
  let expects

  beforeEach(() => {
    wrapper = shallowMount(MyComponent)
    actions = initActions(wrapper)
    expects = initExpects(wrapper)
  })

  it('displays message when there are no todos', () => {
    expects.expectMessage('No todos yet.')
    expects.expectButtonVisible('add-todo')
  })

  it('hides add button when there are 10 todos', () => {
    for (let i = 1; i <= 10; i++) {
      actions.setInputValue('new-todo', `Todo ${i}`)
      actions.clickButton('add-todo')
    }

    expects.expectMessage('You have enough todos for today.')
    expects.expectButtonVisible('delete-todo')
 })

```

## Conclusion
To sum up, encapsulating expectations and actions in separate objects can help make integration tests more modular and easier to manage. By breaking down the test into smaller, more focused functions, we can make the code easier to read and understand. This approach can also make it easier to add new tests or modify existing ones as the application evolves over time.

Furthermore, by using integration tests, we can gain more confidence in the overall behavior of the application and ensure that different components work together as expected. This approach can help catch issues early on in the development cycle, leading to a more reliable and robust application.

Overall, by encapsulating expectations and actions in separate objects and using integration testing, we can create more effective and maintainable tests that help us build better applications.