Yes, it’s that time of year when we prepare pickles for the winter season—so naturally, I’ll be talking a bit about Cucumber. Just like preserving fresh ingredients to savor during the colder months, Cucumber in testing was designed to preserve requirements in a readable format that all team members can understand. But, as any pickling enthusiast knows, not everything fits perfectly in the jar, and Cucumber has its challenges.
This week I attended the online CypressConf 2024 and I was left with more questions than answers after watching all the presentations. One thing stuck to my mind, is it okay to use Gherkin syntax when writing your tests or not? I have seen a very good presentation on the benefits of using the Given-When-Then template, however everyone in the chat, more than 1000 people, seemed very dissatified with using this method. I will be honest with you, I never had to use it. So let’s find out why people hate it so much.
The presentation started like this: one of the most challenging aspects of testing is ensuring that your tests remain maintainable as applications evolve. With web development moving at a rapid pace, companies often transition from one frontend framework to another—like moving from Angular to React or Vue. Rewriting tests every time this happens is costly, inefficient, and prone to errors.
But what if your tests didn’t need to change at all?
The answer lies in test helpers and drivers. These components help you write tests that can work across different frameworks, making your automation efforts reusable, maintainable, and efficient. Combined with the Given-When-Then structure used in Behavior-Driven Development (BDD), these strategies ensure that your tests are clear, concise, and reusable, regardless of the technology used.
What are Test Helpers, Drivers, and Given-When-Then?
Test Helpers are small utility functions that simplify common or repetitive actions in your tests. For instance, you might have a helper function that fills in a form or fetches a specific API response. Helpers keep your tests focused on logic rather than mundane actions.
Test Drivers abstract the interaction between the test and the application under test (AUT). A driver is responsible for managing interactions with UI elements like buttons, text fields, dropdowns, and modals. The goal of a driver is to isolate the specifics of interacting with the UI or APIs, ensuring that the test itself remains focused on what should happen, rather than how it happens.
Given-When-Then is a structured format for writing tests in Behavior-Driven Development (BDD). It helps define the preconditions (Given), the actions performed (When), and the expected outcomes (Then). This structure is useful in improving test readability and providing clarity, especially when collaborating with non-technical stakeholders.
When combined, helpers, drivers, and the Given-When-Then structure allow you to write tests that are reusable across different application frameworks without modification while keeping them easy to understand.
The Benefits of Test Helpers, Drivers, and Given-When-Then in Cross-Framework Testing
1. Decoupling Tests from the Framework
One of the biggest challenges in modern web testing is that front-end frameworks are constantly evolving. A company might start with Angular but migrate to React in a few years. In traditional setups, this would require rewriting tests to accommodate framework-specific logic.
By using drivers to manage UI interactions and helpers for repeated logic, your tests are decoupled from the framework itself. With the Given-When-Then structure, the tests remain clear and focused on the behavior, regardless of what framework underlies the UI.
For example:
- Given: I am on the login page
- When: I submit valid credentials
- Then: I should see the dashboard
This sequence remains the same regardless of whether the app is built with Angular, React, or any other framework.
2. Reusability
With well-constructed helpers and drivers, you don’t need to write new tests when the front-end technology changes. Instead, the same tests will work regardless of whether your app is in Angular, React, Vue, or even Flutter. Using Given-When-Then, the structure stays the same even if the internal workings of the UI shift, as it focuses on user behaviors and outcomes.
3. Maintainability
When tests are separated from the underlying implementation, maintaining them becomes easier. For instance, if a form field changes its structure in the UI, you would only need to update the driver or helper that interacts with it—leaving the rest of your test suite intact. The Given-When-Then structure also makes tests easier to maintain by keeping them descriptive and aligned with the business behavior.
4. Simplified Test Cases
The combination of helpers for repeated actions, drivers for UI interactions, and the Given-When-Then format makes tests not only easier to read but also more maintainable. Helpers can abstract away repetitive details like login sequences or API call setup, while Given-When-Then provides an intuitive way to describe what the test is doing, making it understandable for all stakeholders.
Example: A Driver for UI Interactions in Given-When-Then
Let’s say you are working on an app that uses Angular. The test involves clicking a button and submitting a form. Here’s how the driver can abstract away the interaction, and how Given-When-Then is used for structuring the test:
class FormDriver {
clickSubmitButton() {
return cy.get('button[type="submit"]').click();
}
fillUsername(username) {
return cy.get('input[name="username"]').type(username);
}
fillPassword(password) {
return cy.get('input[name="password"]').type(password);
}
}
Now, your test might look like this:
describe('Login functionality', () => {
it('should allow a user to login', () => {
const formDriver = new FormDriver();
// Given I am on the login page
cy.visit('/login');
// When I fill in valid credentials and click submit
formDriver.fillUsername('testuser');
formDriver.fillPassword('password123');
formDriver.clickSubmitButton();
// Then I should see the dashboard
cy.url().should('include', '/dashboard');
});
});
In this example, Given the login page, When the form is filled and submitted, and Then the dashboard URL is verified.
Example: A Helper for API Mocking in Given-When-Then
Here’s another example. Suppose your app makes API requests to fetch user data, and you need to mock these requests during testing. A helper function could handle this, while your test uses the Given-When-Then structure:
function mockUserApiResponse() {
cy.intercept('GET', '/api/user', { fixture: 'user.json' });
}
describe('User Data Fetch', () => {
it('should display user data correctly', () => {
// Given the user data is available
mockUserApiResponse();
// When I visit the user profile page
cy.visit('/user-profile');
// Then I should see the correct user information
cy.get('.username').should('contain', 'John Doe');
});
});
No matter which front-end framework your app uses to call the API, the helper function remains the same, and Given-When-Then describes the flow of the test logically.
Case Study: Migrating from Angular to React Without Rewriting Tests
Imagine a scenario where a company has a web application built with Angular, and the decision is made to migrate the app to React for better performance and flexibility. This type of migration can be painful, especially when there is a large suite of automated tests tied to Angular-specific logic.
But if those tests were written using helpers, drivers, and the Given-When-Then format, they would still work in the React-based application. For example, a driver that manages button clicks or input field interactions would not need to be rewritten, only adapted slightly if any DOM structure changes. The main test logic—validating business rules, checking workflows, and asserting on API responses—remains untouched.
This is the real power of abstracting your tests: you can migrate the entire front-end of your application without needing to rewrite or overhaul your test suite. The result is saved time, reduced costs, and quicker project delivery.
Best Practices for Using Test Helpers, Drivers, and Given-When-Then
- Keep Drivers Focused on Interactions
Drivers should deal solely with interacting with the system, such as clicking buttons, navigating pages, or inputting text. Avoid putting any test logic into the driver. - Use Helpers for Repeated Logic
If there’s a sequence that you use repeatedly in your tests (e.g., logging in or fetching a certain API response), use helpers to abstract this. Helpers keep tests clean and allow easy modification of repeated logic. - Given-When-Then Structure for Readability
The Given-When-Then structure not only improves readability but also ensures that your tests remain business-driven and understandable by both technical and non-technical stakeholders. - Test Helper Flexibility
Ensure that your helpers and drivers are flexible enough to handle minor changes in the UI or API. They should be written with the idea that the underlying system could change, but the functionality being tested remains constant.
Limitations of Given-When-Then Tools
While the Given-When-Then (GWT) format is a powerful tool in many scenarios, there are situations where it might not be the best choice. Here are some key limitations:
1. Testing Complex Workflows
The GWT format can become cumbersome for complex workflows with multiple dependencies or intricate logic. In these cases, it may result in long, unreadable test cases that are hard to maintain.
- Alternative: State-based or model-based testing approaches can offer a clearer understanding of complex systems.
2. Low-Level Unit Testing
GWT is designed for higher-level behavioral testing. It’s not suited for low-level unit tests that focus on validating individual pieces of code like functions or methods.
- Alternative: A traditional AAA (Arrange-Act-Assert) pattern or direct assertions are more appropriate for unit testing.
3. Non-User-Facing Logic
For deeply technical or internal logic, GWT may feel forced. It’s better suited for user-facing functionality rather than testing backend processes, algorithms, or internal workflows.
- Alternative: Use direct integration tests or unit tests for internal logic.
4. Dynamic or Unpredictable Behavior
GWT struggles to capture highly dynamic behavior, such as performance testing or systems with unpredictable outcomes.
- Alternative: Exploratory testing or performance testing frameworks can capture this type of variability.
5. Overcomplicating Simple Tests
For very simple tests, GWT can add unnecessary complexity. Testing a simple function may not need the full structure of Given-When-Then.
- Alternative: A simple assertion-based test might be more efficient.
6. Non-Behavioral Tests
GWT focuses on behavior, but some tests are more about system state, such as database consistency or configuration. These don’t fit neatly into the GWT format.
- Alternative: State-based testing or direct checks on system states are often better suited.
7. Purely Technical Audiences
GWT might feel excessive when the primary audience is technical, such as developers working on APIs. In these cases, lower-level tests or contract tests may be more appropriate.
- Alternative: Use unit tests, API tests, or contract testing frameworks.
Additional Considerations:
Capturing Non-Textual Requirements
- Limitation: GWT tools primarily rely on text to capture scenarios. For some requirements, particularly cross-functional ones like usability or accessibility, it’s challenging to express everything in words or specific actions that fit the GWT syntax.
- Impact: Teams may need to work around the constraints of text-based descriptions when some requirements would be better illustrated with metrics, diagrams, or examples that don’t translate well into the Given-When-Then format.
- Example: Describing how intuitive a user interface should be or how scalable the system must become under peak loads doesn’t always fit well into “Given-When-Then” statements.
Structured Around Features
- Limitation: GWT pushes teams to organize around features and user stories. While this is useful for behavioral-driven development (BDD), it doesn’t accommodate scenarios that are cross-cutting across multiple features or concern system-wide behaviors.
- Impact: Teams often have difficulty documenting cross-functional aspects like performance, security, or scalability in a way that aligns with how GWT tools organize tests.
- Example: Performance tests that evaluate the entire system under stress, not just specific user actions, are difficult to fit into feature-based GWT structures.
Rigid Syntax Can Be a Barrier
- Limitation: The structured format of GWT, while useful for clarity and consistency, can feel restrictive or overly technical for non-technical team members, such as Business Analysts (BAs). It can be challenging for them to express requirements without getting bogged down by the format.
- Impact: Non-technical members may find it difficult to capture requirements without making the scenario more complex or overly technical.
- Example:
A BA might struggle to express a business requirement in GWT, such as “improving user satisfaction,” which doesn’t naturally fit the Given-When-Then format.
One common misconception behind the popularity of Gherkin syntax is what I call “the Cobol Fallacy.” Just because Gherkin’s Given-When-Then format is readable to non-technical people doesn’t mean it enables them to write effective tests. After all, the average stakeholder already struggles to convert their thoughts into well-defined requirements.
Asking them to translate these into test scripts, even in simple syntax, is like expecting them to write code—a leap they’re often neither prepared nor equipped to make.
This brings us to the reality of Gherkin: while it’s marketed as a bridge between business and technical teams, it frequently ends up adding complexity without necessarily enhancing collaboration or test quality.
The point of Gherkin syntax is to establish a ubiquitous language. It creates a shared way of describing features that’s accessible to everyone involved in a project: developers, product managers, business analysts, and business stakeholders. This shared language is most effective at the acceptance testing level. Here, Gherkin helps define scenarios as acceptance criteria that can later be automated, enabling consistent communication about the functionality across teams. Tools like SpecFlow or Cucumber even generate reports that keep stakeholders up-to-date on the latest state of features, bridging the gap between technical and business perspectives.
However, not everyone is thrilled about BDD and Gherkin. Some testers express frustration over how much time goes into crafting Gherkin scenarios compared to simply executing tests directly. As one tester put it, “I feel like I spend more time figuring out what/how to write BDD than actually executing the test itself.” For some, the structured, rigid syntax of BDD ends up feeling like more of an obstacle than an asset, especially in cases where straightforward coding might be faster or simpler.
Conclusion: Knowing When to Use Given-When-Then
In summary, the Given-When-Then (GWT) format excels in higher-level behavioral tests, particularly when applied in Behavior-Driven Development (BDD) for user-facing features. It helps communicate intent clearly and fosters collaboration between developers, testers, and stakeholders. However, GWT may not be the right tool for technical, low-level, or highly complex logic tests, where it can sometimes add unnecessary complexity.
The key to leveraging GWT effectively is knowing when it adds value to the testing process by clarifying behavior and when a more technical, detailed approach is required. Choose what is best for your project—whether that means using GWT for clear communication on user behavior or opting for other testing strategies for more intricate, lower-level scenarios.
As applications continue to evolve, GWT, along with test helpers and drivers, allows you to write tests that are maintainable, reusable, and decoupled from specific frameworks. By focusing on behavior-driven testing, you can future-proof your test suite, ensuring that it remains robust across different technologies and changes in implementation details. This flexibility allows you to keep up with fast-moving development environments while maintaining high-quality standards for your software.








Leave a comment