9

Lets say I have some sort of unit tests like this:

let myApi = new Api();

describe('api', () => {

  describe('set()', () => {
    it('should return true when setting a value', () => {
      assert.equal(myApi.set('foo', 'bar'), true);
    });
  });

  describe('get()', () => {
    it('should return the value when getting the value', () => {
      assert.equal(myApi.get('foo'), 'bar');
    });
  });

});

So now I have 2 unit tests. One sets a value in an API. The other one tests to make sure the proper value is returned. However the 2nd test is dependent on the first one. Should I add in a .set() method in the 2nd test before the get() with the sole purpose of making sure the 2nd test is not dependent on anything else?

Also, in this example should I be instantiating myApi for each test instead of doing it once before the tests?

2 Answers 2

17

Yes, it's bad practice. Unit tests need to run independently of each other, for the same reasons that you need any other function to run independently: you can treat it as an independent unit.

Should I add in a .set() method in the 2nd test before the get() with the sole purpose of making sure the 2nd test is not dependent on anything else?

Yes. However, If these are just bare getter and setter methods, they don't contain any behavior, and you really shouldn't need to test them, unless you have a reputation for fat-fingering things in such a way that the getter/setter compiles but sets or gets the wrong field.

3
  • In my example, lets say myApi is an instantiated object. Should I reinstantiate myApi in each unit test? Or is reusing it between tests have potential to cause test to give false positives, etc. And yes my example is a simplified getter/setter thing but in reality it would obviously be much more complicated. Commented Nov 5, 2016 at 0:53
  • 1
    @JakeWilson There is a book called Pragmatic Unit Testing that goes over the basics of unit testing, such as how to avoid tests interfering with each other, etc.
    – rwong
    Commented Nov 5, 2016 at 7:25
  • Another example : if you use JUnit, the order of your function is not defined by the order you wrote them in theclass. @JakeWilson If your api is stateless you can reuse it, if not reinstantiate it.
    – Walfrat
    Commented Nov 5, 2016 at 16:10
2

Try to follow the arrange-act-assert structure for each test.

  1. Arrange your objects etc. and put them into a known state (a test fixture). Sometimes, this phase includes asserts to show that you are in fact in the state you think you are.
  2. Act, that is: perform the behaviour you are testing.
  3. Assert that you got the expected result.

You tests don't bother to create a known state first, so they are meaningless in isolation.

Also, unit tests don't necessarily test a single method only – unit tests should test a unit. Usually, this unit is a class. Some methods like get() only make sense in combination with another.

Testing getters and setters is sensible, in particular in dynamic languages (just to make sure they're actually there). To test a getter, we need to supply a known value first. This might happen through the constructor, or through a setter. Or viewed differently: testing the getter is implicit in tests of the setter and of the constructor. And the setter, does it always return true, or only when the value was changed? This might lead to the following tests (pseudocode):

describe Api:

  it has a default value:
    // arrange
    api = new Api()
    // act & assert
    assert api.get() === expected default value

  it can take custom values:
    // arrange & act
    api = new Api(42)
    // act & assert
    assert api.get() === 42

  describe set:

    it can set new values:
      // arrange
      api = new Api(7)
      // act
      ok = api.set(13)
      // assert
      assert ok === true:
      assert api.get() === 13

    it returns false when value is unchanged:
      // arrange
      api = new Api(57)
      // act
      ok = api.set(57)
      // assert
      assert ok === false
      assert api.get() === 57

Reusing the state from a previous test would make our tests quite fragile. Reordering the tests or changing the exact value in a test might cause apparently unrelated tests to fail. Assuming a specific state can also hide bugs if it causes tests to pass that should actually fail. To prevent this, some test runners have options to run the test cases in a random order.

However, there are cases when we do reuse the state provided by the previous test. In particular when creating a test fixture takes a lot of time do we combine many test cases into a test suite. While these tests are more fragile, they might still be more valuable now because they can be performed more quickly and more often. In practice, combining tests is desirable whenever the tests involve a manual component, when a large database is needed, or when testing state machines.

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.