When to use mock objects in unit tests

后端 未结 5 883
北恋
北恋 2020-12-04 02:27

I\'m aware that there are many questions about mocking and testing, but I didn\'t find any that helped me perfectly, so I still have problems understanding the follwing:

相关标签:
5条回答
  • 2020-12-04 02:31

    To answer the first part of your question

    If now I want to unit test my ProcessClass do I consider Citizen as an external feature that has to be mocked, or do I simply just create a Citizen for test purposes?

    Without knowing more about Citizen it is hard to tell. However, the general rule is, that mocking should be done for a reason. Good reasons are:

    • You can not easily make the depended-on-component (DOC) behave as intended for your tests.
    • Does calling the DOC cause any non-derministic behaviour (date/time, randomness, network connections)?
    • The test setup is overly complex and/or maintenance intensive (like, need for external files)
    • The original DOC brings portability problems for your test code.
    • Does using the original DOC cause unnacceptably long build / execution times?
    • Has the DOC stability (maturity) issues that make the tests unreliable, or, worse, is the DOC not even available yet?

    For example, you (typically) don't mock standard library math functions like sin or cos, because they don't have any of the abovementioned problems. In your case, you need to judge whether just using Citizen would cause any of the abovementioned problems. If so, it is very likely better to mock it, otherwise you better do not mock.

    0 讨论(0)
  • 2020-12-04 02:32

    More often than not, mocking is used to replace actual calls that are hard to duplicate in testing. For instance, assume ProcessClass makes a REST call to retrieve Citizen information. For a simple unit test, it would be hard to duplicate this REST call. However, you can "mock" the RestTemplate and dictate the different types of returns to insure your code would handle the 200's, 403's, etc. Additionally you could change the type of information to then also test your code to insure that bad data is handled, like missing or null information.

    In your case you can actually create a Citizen, and then test that the Citizen is an object in the list or that getByName returns the proper object. So mocking is not needed in this example.

    0 讨论(0)
  • 2020-12-04 02:42

    If they are mocked, how would I test the method to get the object by its name, since the mock object is not containing the parameters?

    You can mock the call to getName, using mockito for example:

    Citizen citizen = mock(Citizen.class);
    when(citizen.getName()).thenReturn("Bob");
    

    Here is an example of a test for your method

    ProcessClass processClass = new ProcessClass();
    
    Citizen citizen1 = mock(Citizen.class);
    Citizen citizen2 = mock(Citizen.class);
    Citizen citizen3 = mock(Citizen.class);
    
    @Test
    public void getByName_shouldReturnCorrectCitizen_whenPresentInList() {
        when(citizen1.getName()).thenReturn("Bob");
        when(citizen2.getName()).thenReturn("Alice");
        when(citizen3.getName()).thenReturn("John");
    
        processClass.addCitizen(citizen1);
        processClass.addCitizen(citizen2);
        processClass.addCitizen(citizen3);
    
        Assert.assertEquals(citizen2, processClass.getByName("Alice"));
    }
    
    @Test
    public void getByName_shouldReturnNull_whenNotPresentInList() {
        when(citizen1.getName()).thenReturn("Bob");
    
        processClass.addCitizen(citizen1);
    
        Assert.assertNull(processClass.getByName("Ben"));
    }
    

    Note:

    I would recommend mocking. Let's say you write 100 tests where you instantiate a Citizen class this way

    Citizen c = new Citizen();
    

    and a few months later, your constructor changes to take an argument, which is an object itself, class City for example. Now you have to go back and change all these tests and write:

    City city = new City("Paris");
    Citizen c = new Citizen(city);
    

    If you mocked Citizen to start with, you wouldn't need to.

    Now, as it is POJO and its constructor of the getName method might not change, not mocking should still be ok.

    0 讨论(0)
  • 2020-12-04 02:50

    In your particular example, no, you would not need to mock anything.

    Let's focus on what you would test:

    1. a test where you add and retrieve one Citizen
    2. add 2 Citizens, retrieve one
    3. pass null instead of citizen, and make sure your code doesn't break.
    4. add two citizens with the same name, what would you expect to happen then?
    5. add a citizen without a name.
    6. add a citizen with a null name

    etc etc

    You can already see a number of different tests you can write.

    To make it more interesting you could add some code to your class which exposes a read-only version of your citizenList, then you could check that your list contains exactly the right things.

    So, in your scenario you don't need to mock anything as you don't have external dependencies on another system of some kind. Citizen seems to be a simple model class, nothing more.

    0 讨论(0)
  • 2020-12-04 02:57

    As you're writing new code (along with the new unit tests) or refactoring existing code, you want to be able to run the unit tests over and over to be reasonably confident existing functionality was not broken. Therefore, the unit tests must be stable and fast.

    Suppose the class to be tested depends on some external resource such as a database. You make a code change and the unit tests are suddenly failing. Did the unit tests break because of a bug you just introduced, or because the external resource is not available? There is no guarantee the external resource will always be available so the unit tests are unstable. Mock the external resource.

    Also, connecting to an external resource can take too much time. When you eventually have thousands of tests which connect to various external resources, the milliseconds to connect to the external resource add up, which slows you down. Mock the external resource.

    Now add a CI/CD pipeline. During the build the unit tests fail. Is the external resource down or did your code change break something? Perhaps the build server doesn't have access to an external resource? Mock the external resource.

    0 讨论(0)
提交回复
热议问题