-1

I am new in Unit Testing and I have sometimes such situations with multiple conditions. However, I am not sure if I re-mock or verify the same cases for each test.

For example, I am trying to write Unit Tests for the following service method:

public void create(Request request) {
    
    // code omitted

    if (!employeeService.existsByUuid(uuid)) {
        throw new EntityNotFoundException("Not found");
    }

    EmployeeDTO employee = employeeService.save(...);
    
    if (!departmentService.existsByUuid(employee.getDepartment())) {
        throw new EntityNotFoundException("Not found");
    }
}

I think I need to write my tests for the following scenarios:

1. when employeeService.existsByUuid(uuid) == false, then throw new EntityNotFoundException. then verify employeeService.save() and departmentService.existsByUuid() is never called.

2. when employeeService.existsByUuid(uuid) == true then employeeService.save() is called and I assert the values. and then verify employeeService.save() and departmentService.existsByUuid() is never called.

3. when departmentService.existsByUuid() == false then throw new EntityNotFoundException. At this stage, I also mock employeeService.existsByUuid(uuid) as true so that test passes the first condition. However, I am not sure if do I need to assert the second part; employeeService.save() is called and I assert the values. Do I assert of the returned values or just verify that method is called 1 time. Because I already asserted its value and the 3rd test is just for the 3rd condition.

Any idea for this kind of scenarios when we have multiple condition and may need to re-test the same condition again and again?

  • 1
    Ideally you should write one separate test for each different scenario. For things that need to be set up every time, you can use `@Before` in your test class. – QBrute Mar 16 '22 at 11:43
  • @QBrute Thanks, but I mean not general things that can be collected in `@Before` section. So, do you mean that I need to perform assertions for `employeeService.save()` part even if I test the first or the third condition? Or just verify it is called or not according to the situation. Any idea? –  Mar 16 '22 at 11:51

2 Answers2

1

You should not try to test your code line by line, but with cases that cover a single meaningful scenario. So if you already have a case which checks a condition, you don't have to repeat those asserts in other test cases.

In your example I think these could be the core cases:

  1. if the UUID does not exist, an exception is thrown and the employee is not saved
  2. if the UUID exists, all the employee fields are saved correctly
  3. if the employee is saved, but the employee's department does not exist an exception is thrown

To test them you could do something like this:

EmployeeService employeeService = mock(EmployeeService.class);

case 1:

when(employeeService.existsByUuid(employeeUuid)).thenReturn(false);   
try {
    testObject.create(request);
    fail();
}
catch(EntityNotFoundException e) {
    verify(employeeService, never()).save(...);
}

case 2:

when(employeeService.existsByUuid(employeeUuid)).thenReturn(true); 
when(employeeService.existsByUuid(departmentUuid)).thenReturn(true);    
testObject.create(request);
verify(employeeService).save(field1, field2, ...);

case 3:

when(employeeService.existsByUuid(employeeUuid)).thenReturn(true);   
when(employeeService.existsByUuid(departmentUuid)).thenReturn(false);   
try {
    testObject.create(request);
    fail();
}
catch(EntityNotFoundException e) {
    // success
}

BTW you can also indicate expected exceptions in the @Test annotation, but then you cannot do any further checking on the results:

@Test(expected = EntityNotFoundException.class)
public void test3() {
    when(employeeService.existsByUuid(employeeUuid)).thenReturn(true);   
    when(employeeService.existsByUuid(departmentUuid)).thenReturn(false);   
    testObject.create(request);
}
Adriaan Koster
  • 15,870
  • 5
  • 45
  • 60
  • Do you mean that I **DON'T need** to perform assertions for `employeeService.save()` part when I test the first or the third condition and just verify if `employeeService.save()` is called or not (according to the condition) when I test first or third condition. Any idea? –  Mar 16 '22 at 11:55
  • I think for the first case, it is an important requirement that the employee is NOT saved. For the second case, you should check that the employee is saved CORRECTLY. And for the third case, saving is not relevant. – Adriaan Koster Mar 16 '22 at 11:59
  • Thanks a lot, first I do not use try-catch and just use JUnit5 notation for the exception testing. The points that I am not sure is listed below, could you pls clarify me one by one? –  Mar 16 '22 at 12:14
  • **1.** When I test first condition, I generally prefer to use verify for the remained method calls. Is that bad? Or except from testing case, should I omit the verifying of method calls after my case is executed? –  Mar 16 '22 at 12:16
  • **2.** when I test a part, should I just mock necessary part and no need to verify other parts (cases)? –  Mar 16 '22 at 12:21
  • 1. Verify is not bad, but sometimes not needed. For instance in case 1, the employeeService is stubbed to return 'false' and we expect the code to throw an exception. If the exception happens, the behavior is as expected. No need to do additional verifications there. But for the save action, we want to explicitly verify that it is NOT called, so we need to verify that. – Adriaan Koster Mar 16 '22 at 13:10
  • 2. Try to keep each test case as focused as possible (whilst keeping it readable). No need to repeat asserts or verifications that are not needed for that specific case. – Adriaan Koster Mar 16 '22 at 13:11
  • Extra free tip: if your code is creating a new instance and passing it into one of its dependencies, you can check the values using an ArgumentCaptor. – Adriaan Koster Mar 16 '22 at 13:12
  • Regarding the try-catch, it is needed in case 1, because you want to verify something AFTER the exception was thrown. If you use the annotation, then you cannot perform any additional checks as I already mentioned. For case 3, you can do without try-catch. – Adriaan Koster Mar 16 '22 at 13:13
  • I use ArgumentCaptor, but do lot posted here to make the code simple. Thanks a lot amigo, very useful answer and comments. –  Mar 16 '22 at 13:40
  • Any help about **[this issue](https://stackoverflow.com/questions/48746002/how-to-verify-a-method-was-called-when-testing-an-exception)**? –  Mar 17 '22 at 07:12
  • There is a comment saying the same thing I have mentioned: "you need to catch the exception and perform assertions after" (https://stackoverflow.com/questions/48746002/how-to-verify-a-method-was-called-when-testing-an-exception#comment84491745_48746002) – Adriaan Koster Mar 18 '22 at 11:19
  • Thanks, but I do not prefer using try-catch and I think there may be another alternative. Any idea? –  Mar 18 '22 at 11:37
  • You can use assertThrows as explained by HariHaravelan – Adriaan Koster Mar 18 '22 at 14:06
  • You can also use an ExpectedException rule. See this article for an overview: https://blog.aspiresys.pl/technology/different-ways-of-testing-exceptions-in-java-and-junit/ – Adriaan Koster Mar 18 '22 at 14:12
0

You can use mockito verify and assert throws to test your objectives something like below

    @Test
    public void testOne(){
        when(employeeService.existsByUuid(uuid)).thenReturn(false);

        assertThrows(EntityNotFoundException.class, () -> {
            create(request);
        });

        verify(employeeService, times(0)).save(eq(empObj));
        verify(departmentService, times(0)).existsByUuid(eq(departmentObj));
    }




    @Test
    public void testTwo(){
        when(employeeService.existsByUuid(uuid)).thenReturn(true);
         when(departmentService.existsByUuid(uuid)).thenReturn(true);

        create(request);

        verify(employeeService, times(1)).save(eq(empObj));
        verify(departmentService, times(1)).existsByUuid(eq(departmentObj));
    }

    @Test
    public void testThree(){
        when(employeeService.existsByUuid(uuid)).thenReturn(true);
        when(departmentService.existsByUuid(uuid)).thenReturn(false);

        assertThrows(EntityNotFoundException.class, () -> {
            create(request);
        });

        verify(employeeService, times(1)).save(eq(empObj));
        verify(departmentService, times(1)).existsByUuid(eq(departmentObj));
    }
HariHaravelan
  • 1,041
  • 1
  • 10
  • 19