3

I'm writing the sort of tests which go through a whole user scenario e.g.

  1. User clicks "edit profile"
  2. User edits their name

etc.

However, I find myself needing often needing to test small variations on the same workflow. For example (3) could be "User saves changes" or it could be "user cancels changes" etc.

It feels like a serious DRY violation to just have multiple different test cases which are very similar except at a few decision points.

On the other hand, I don't have a good pattern to hand for essentially saying "repeat this test up to stage (2), but then do something different" or "do the same things as in this previous test, but under this slightly different condition", while maintaining the readability that is crucially important in a testing context.

I realise this is quite a general question, but any pointers towards testing patterns that might help in this context would be greatly appreciated.

4

1 Answer 1

2

There is a balance to find between reuse and repetition. I would not necessarily qualify your description of a DRY violation, as there are merits to replicating a similar setup in different scenarios. That being said. there are a few patterns and common practices that deal with this issue.

  1. You can live with the duplication. As mentioned above, duplication is not inherently bad, especially in tests. There are lots of merits to repeating test setups as it makes each test independent from one another, allowing its reader to grasp the entire context from simply reading the test itself and nothing else. As referenced by Doc Brown in the comments of your question, this is referred to as DAMP (Descripting And Meaningful Phrases), a wordplay with DRY (Don't Repeat Yourself).

  2. You can implement test fixtures for your scenarios. For instance, you could create a fixture that authenticates a user, which would allow your different tests to use this fixture to set up an initial state where a user is authenticated and ready to perform an action, then leave the actual action in each test. This reduces the boilerplate required of setting up the entire authentication context in each test if the relevant action is editing their profile, but still allows individual tests to document the precise action they are testing.

  3. You can use a RSpec/Jasmine-like style to write your tests. This style puts a strong emphasis on decoupling the context from the assertion, making it easy to nest multiple levels of tests. For example:

     describe("given an authenticated user", () => {
       beforeEach(() => {
         user.authenticates()
       });
    
       describe("and the user is editing their profile", () => {
         beforeEach(() => {
           user.editProfile()
         });
    
         it("should allow editing their name", () => {
           // ...
         });
    
         describe("and even more nesting", () => {
           // ...
         });
       });
     });
    

    While this does reduce duplication, it requires sharing state between multiple different scopes and can make for, quite frankly, hard to read tests if you are not being careful.

2
  • 1
    The RSpec/Jasmine suggestion is good, but dependent on the test framework being used. Nov 2, 2021 at 18:27
  • 1
    @GregBurghardt Yep that's a good point. I found that quite a few test frameworks supported a similar style, e.g. JUnit 5 with the @Nested annotation. However, I did not find a framework-agnostic term for this style, but I am up for suggestions Nov 2, 2021 at 18:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.