10

Background: I am new to testing in general, and have been studying it in context of JavaScript, specifically React.js, front-end development (actually new to this as well). For the question, I have these 2 similar cases:

Case 1: My code and a library

I'm using UI library and aside from its decent design, I want to leverage its form validation utilities. I want to test this validation, but not sure how to do it.

  • Option 1: Just test for the behaviors I expect

    It would be testing if my field (which is a sub-module of the library itself) receives the library-specific classes for fields with errors as expected, and if these errors prevent from calling the submit handler I passed to the form (also sub-module of the library).

    • Problem A with Option 1

      I am very likely to be duplicating tests that are already covered by the library itself. (e.g. I am testing if the field receives the error classes when the input is invalid when the library itself has tested if it does so, given the right configuration.)

    • Problem B with Option 1

      It slightly couples the test code to the library, i.e. I have to use library-specific classes and markup to evaluate my code.

      Okay, this is actually a side-question, is this a bad thing for a test? Is this making the test brittle, or is what they call 'contract' that is actually necessary for unit tests?

  • Option 2: Test which sub-module is used and what configuration is passed

    • Problem A with Option 2

      This is much more brittle, I think, It's almost like repeating the implementation (i.e. repeating the type sub-module used, and the configuration passed to it.)

    • Problem B with Option 2

      Same as Option 1's Problem A -- coupling test to the library's API, specifically its configuration API. (Again, not sure if this is just test being brittle, or contract being written)


Case 2: My code and its sub-component

I have my good 'ol to do app. Its to do list component has addTodo() method and it passes to its sub-component to do field. I want to test its feature to add todo item, but not sure on how to do this either.

  • Option 1: Again, just test for the behaviors I expect

    Test `to do app` that if input and submit with sub-component `to do field`, another `to do item` is added. (This is implemented by passing `addTodo()` method from `to do app` to `to do field` as onSubmit handler)
    
    • Problem A with Option 1

      If I'll do this, is there still a point in unit testing the to do field alone? If I already unit-tested to do field that it calls any onSubmit handler upon submitting, wouldn't this to do app test case indirectly repeating to do field's test case??

  • Option 2: Test if correct sub-component is used and what configuration is passed (Similar to Case 1's option 2)

    Its specs would look like this:

    • Unit test to do app to assert...
      • that to do app's addTodo() method adds a todo item properly
      • that to do app renders to do field sub-component
      • and that to do app passes addTodo() to as onSubmit handler to to do field
    • then also unit test to do field to assert that...
      • that to do field calls the onSubmit, which is received from parent component, during submit

The Question

Which of these options are better, especially if I favor BDD over TDD? Also, please correct me if you noticed I misunderstood anything. Thank you.

8
  • 2
    I'd avoid testing 3rd party library code in general. Your approach concerns me though because you're coupling your validation (which is business logic) to a specific UI. Change UIs (or even to another library) and it seems like you'll be throwing your business logic away.
    – Andy
    Jul 11, 2017 at 21:09
  • What would be the better approach? Jul 11, 2017 at 21:29
  • Keep your business logic in a business library that doesn't have any dependencies on UI. An N-layer, DDD, onion, whatever you want to call it approach.
    – Andy
    Jul 11, 2017 at 21:31
  • Hmm, my understanding is that in BDD, in order to test the behavior, you inevitably involve the UI, this is what I've observed in JS front-end development. Jul 11, 2017 at 22:16
  • Not sure where you got that idea, that sounds off to me. MSpec for example is a BDD framework. Its mostly a shift in your thinking about how to build the unit test. Integration / acceptance tests may involve the UI, but nothing in BDD to me suggests it has to involve the UI. en.wikipedia.org/wiki/Behavior-driven_development
    – Andy
    Jul 11, 2017 at 22:42

3 Answers 3

1

Unit testing refers to testing individual units or components of your code in isolation from the rest of the application. The goal is to verify that each unit or component is working correctly on its own, and to identify any issues early in the development process.

In the context of testing a library or sub-component, the boundaries of unit testing depend on the specific design of your code and the library or sub-component being used. Here are some general guidelines for testing in these cases:

Case 1: My code and a library

Option 1: Just test for the behaviors you expect

This option involves testing the expected behavior of your code when interacting with the library or sub-module. To avoid duplication of tests, you may want to focus on testing the specific behaviors of your code that are unique to your application, rather than testing the behavior of the library itself. It is generally acceptable for unit tests to depend on the API of the library or sub-module being tested, but you should be mindful of how much the test code depends on the specific implementation details of the library or sub-module.

Option 2: Test which sub-module is used and what configuration is passed

This option involves testing the specific implementation details of the library or sub-module being used. This can be more brittle, as it is more sensitive to changes in the implementation of the library or sub-module. You should consider whether testing the specific implementation details adds value to your testing strategy.

Case 2: My code and its sub-component

Option 1: Test for the behaviors you expect

This option involves testing the expected behavior of your code when interacting with the sub-component. To avoid duplication of tests, you may want to focus on testing the specific behaviors of your code that are unique to your application, rather than testing the behavior of the sub-component itself.

Option 2: Test if correct sub-component is used and what configuration is passed

This option involves testing the specific implementation details of the sub-component being used. This can be more brittle, as it is more sensitive to changes in the implementation of the sub-component. You should consider whether testing the specific implementation details adds value to your testing strategy.

When deciding which approach to take, it can be helpful to consider the following questions:

What are the most important behaviors or features of my code that I want to test? How much does my code rely on the library or sub-component being tested? How much does the implementation of the library or sub-component being tested change over time? How much do I care about the specific implementation details of the library or sub-component being tested? By answering these questions, you can determine the most appropriate approach for testing your code in the context of a library or sub-component.

0

You are testing a user interface. This is exactly what functional testing was created to do. You want to test the behavior of the interface you've created, specifically:

  • When the screen is first loaded, is the submit button visible/selectable? If it shouldn't be, you can test for that.
  • Enter invalid data into your text field. Use all the different types of invalid data that you have configured to check. The submit button should probably be visible/selectable, but clicking it should generate some kind of error message. Check that the error message displays as you expect it to.
  • Enter valid data into your text field. The submit button should be visible/selectable, and clicking on it should produce the correct result. Check that the correct result is displayed.

Using functional testing like this prevents coupling to your library. Changing the library should result in these tests passing. Even dropping the library and rolling your own validation routines should result in these tests passing.

If you haven't chosen a functional testing tool, you may want to consider Selenium. It will enable you to write and run functional tests.

0

I think you have outlined approaches in your Case 2 above. What you are getting at is the distinction between unit and integration tests. (I actually find that the language of TDD vs. BDD is not that helpful here.) In a "unit test" test, the behavior of a specific piece of code is validated; and an integration tests, the interaction between different pieces of code is validated.

Let's say that the React validation library changes in such a way that it breaks your app. If you upgrade your library, you'd like a test to fail in this case. And ideally, you wouldn't want all your tests to fail.

Like you say, you can write specific tests that validate individual the behaviors of your code, such as:

a. if I enter bad data a css class gets applied to an input, and 
b. if there's a css class on an input the submit button is disabled. 
   If your library does this, it's tested there, right?
c. if the submit button is disabled the form cannot be submitted, etc. 

These very specific behavior tests are nice when they fail, as it's clear what's going on. The downside is that sometimes the chain, or coupling between them, can have flaws: a step can be left out, or the contract with an external tool can change slightly and then go undetected with the unit test. Or it can just be hard for the reader to piece them all together.

And a callout to (b) above. If a your code is dependent on a specific library behavior, you need to figure out how you will maintain trust in it. If it's the main behavior of the library, maybe you can just trust it; but if it's a less central piece of behavior, you may want to write your own (redundant) test. I seldom find that testing that a configuration setting is correct raises my confidence much, but people do in fact do this.

Consider a higher level example (perhaps via Selenium) that tests that the system behaves within the context of the app: entering a specific value in an input box shows an error and will not submit to the server. It's nice as it's not tied to implementation details: if a css class is changed globally, the test stays green if the app still works. The downside is that the test is covering a wide swatch of behavior, so it will be more brittle than a unit test. If any of the units tests above fail, this integration test will also probably fail. This can turn people off to these tests, but in fact, this is the only test that will catch some subtle change in your external library that you missed when upgrading.

I recommend a testing strategy include a thoughtful combination of different types of tests, balancing the benefits of these two specific approaches.

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