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;
}
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
}
}
A validation service 🔍
public class VerificationService {
public boolean shipmentIsntValid(String owner, String productCode, int count) {
return owner.isEmpty()
|| productCode.isEmpty()
|| count < 0;
}
}
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();
}
}
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);
}
}
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);
}
}
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.
Top comments (0)