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:
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:
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.
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.
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.
In your particular example, no, you would not need to mock anything.
Let's focus on what you would test:
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.
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.