DEV Community

loading...
Cover image for How To Unit Test Like a Pro

How To Unit Test Like a Pro

Abdulcelil Cercenazi
I enjoy learning and teaching, I am currently working as a Software developer using Spring and Vanilla JavaScript.
・3 min read

What Is Unit Testing❓ 📦

Testing a 'unit' in the system separately from other units.

But What Does a Unit Mean❓

There are a lot of different opinios regarding the answer to this question.

  • Some people say a unit is a function, some say it's a class.

🟢 However, the definition I find most logical is the one describing a unit as a 'behavior' of the system.

  • It's a scenario that has meaning in the business logic.

  • For example, create a new Shipment using the client's inputs, and save them to the database.

    • not really save, but fake it (mock it) we will see it a bit later

But Why Does The Behavior Description Seems The Most Logical❓

Tests would be closer to real-life✔️

You wouldn’t write useless tests. (for example, a useless null pointer check) ✔️

We can refactor our code without breaking tests since we test the outputs of a unit and not its inner workings ✔️

However, there are some things that we might not want to test in a unit 🔴

Like what?

Most commonly, dependencies on the outer world.

  • Database connection (get me the record with this Id)
  • API calls (call Twitter's API and get all my tweets)

Those dependencies we need to mock.
Mocking is defining how a dependency should act in certain conditions.

  • oh dear database connection, please return this object if your x method got called with the y parameter.

Let's look at an example ⚙️

We have a Shipment class 📦

@RequiredArgsConstructor  
public class Shipment {  
   private final String productCode;  
   private final String owner;  
   private final int count;  
}
Enter fullscreen mode Exit fullscreen mode

Shipment Repository for database operations 🗃

public class ShipmentRepository {  
    public Optional<Shipment> findShipment(String owner, String productCode) {  
        // mock method  
  return Optional.empty();  
    }  
    public void save(Shipment shipment) {  
        // mock method  
  }  
}
Enter fullscreen mode Exit fullscreen mode

A validation service 🔍

public class VerificationService {  
    public boolean shipmentIsntValid(String owner, String productCode, int count) {  
        return owner.isEmpty()  
                || productCode.isEmpty()  
                || count < 0;  
    }  
}
Enter fullscreen mode Exit fullscreen mode

The Shipment service 🥋

@RequiredArgsConstructor  
public class ShipmentService {  
    private final VerificationService verificationService;  
    private final ShipmentRepository shipmentRepository;  

    public String createAndSaveShipment(String owner, String productCode, int count){  
        if (verificationService.shipmentIsntValid(owner, productCode, count))  
            return "Shipment isn't valid";  
        if (shipmentExistInDB(owner, productCode))  
            return "Shipment Not Found";  
        createNewShipment(owner, productCode, count);  
        return "Shipment Is Created";  
    }  
    private void createNewShipment(String owner, String productCode, int count) {  
        Shipment shipment = new Shipment(owner, productCode, count);  
        shipmentRepository.save(shipment);  
    }  
    public boolean shipmentExistInDB(String owner, String productCode) {  
        return shipmentRepository.findShipment(owner, productCode).isPresent();  
    }  
}
Enter fullscreen mode Exit fullscreen mode

For the unit testing, we will use Junit5 and Mockito.

Let's look at a bad example of Unit Tests ❌

@ExtendWith(MockitoExtension.class)  
public class UniTestTests {  
    @Mock  
  ShipmentRepository shipmentRepo;  
    @Test  
  public void givenData_DetectValidity(){  
        VerificationService verificationService = new VerificationService();  
        boolean result = verificationService.shipmentIsntValid("jhon", "3HFF", 2);  
        assertFalse(result);  
    }  
    @Test  
  public void givenData_DetectShipmentExistsInDB(){  
        ShipmentService shipmentService = new ShipmentService(new VerificationService(), shipmentRepo);  
        Shipment shipment = new Shipment("ahmed", "3HKK", 4);  
        doReturn(Optional.of(shipment)).when(shipmentRepo).findShipment("ahmed", "3HKK");  

        boolean result = shipmentService.shipmentExistInDB("ahmed", "3HKK");  
        assertTrue(result);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Why are those bad unit tests? ⚠️

  • ShipmentService and VerificationService are two closely related methods that work together to produce a single behavior (adding a new Shipment).
  • However, we are testing them in isolation, both in separate tests.

Now, Let's look at a good unit testing example ✅

@ExtendWith(MockitoExtension.class)  
public class UniTestTests {  
    @Mock  
  ShipmentRepository shipmentRepo;  
    @Test  
  public void givenShipmentData_CreateNewShipment(){  
        VerificationService verificationService = new VerificationService();  
        ShipmentService shipmentService = new ShipmentService(verificationService, shipmentRepo);  

        doReturn(Optional.empty()).when(shipmentRepo).findShipment("jalil", "3HXX");  
        doNothing().when(shipmentRepo).save(any());  

        String result = shipmentService.createAndSaveShipment("jalil", "3HXX", 10);  
        assertEquals("Shipment Is Created", result);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

To Sum it in Few Words 🖋

A unit test is meant to validate how feature behavior in the system.
It could be a bunch of classes and functions interacting together to produce this behavior.

Source Code on 🔗GitHub

Discussion (0)