Unit testing in Development: Overview of unit testing in Pecha repository

Author: Tenzin Tsering, Tenzin Delek

INTRODUCTION

In software development, the inherent uncertainty surrounding application functionality. development can be unpredictable. Imagine working for days on a new feature, merging your code, and then crossing your fingers hoping nothing breaks. Or worse, discovering later that a tiny bug slipped through and messed up the whole app. That’s a nightmare for developers and users alike. This is where unit testing comes into play.

The magic happens with test coverage. The more you test, the fewer surprises you get. It’s like proofreading an essay, you don’t just skim it. you check every paragraph for mistakes. High coverage means you’ve tested most (or all) of your code’s logic, which makes your app way more reliable.

Unit testing also forces you to write cleaner code. If your code is a tangled mess, it’s hard to test. So you naturally start breaking things into smaller, focused pieces that do one job well. This makes your code easier to read, fix, and improve later. Plus, tests act like living documentation, anyone reading your tests can instantly see how your code is supposed to work.

In short, unit testing isn’t just a “nice-to-have”, it’s how you turn “I think it works” into “I know it works.”

WHY UNIT TESTING IS NEEDED AND WHERE IT HELPS

Unit testing is crucial practice in the field of software development and it helps in various fields such as

  1. Ensure Code Reliability

    • Unit testing involves testing individual components or units of code such as functions and methods to verify that they perform as intended. This ensures that the smallest parts of the application works correctly before merging into the main development repository.
  2. Helps with Refactoring

    • As developers improve or refactor of code, unit tests acts as a safety net ensuring that changes in the code don’t break existing functionality
  3. Facilitates Faster Debugging

    • When test fails, it’s clear where the problem is located, which automatically narrows down the debugging process
  4. Improve Code Quality

    • Writing unit tests forces developers to think more about the design and structure of their code. Well-test codes are often more modular, easier to understand, and more maintainable.
  5. Helps in Agile Development

    • Unit testing is vital in agile development where development cycles are short, and iterative changes happen frequently. It helps ensure that new features or changes don’t break the existing functionality.

DIFFERENT LIBRARIES/TOOLS FOR UNIT TESTING

  • Frontend:
    • Jest: Used for Javascript, React, Vue, Angular, Node.js
    • Mocha: Used for Javascript
    • React Testing Library: React
  • Backend:
    • Jest: Node.js, Express
    • Pytest: Python (Django, Flask, FastAPI)
    • Postman: API testing

HOW UNIT TESTING IS IMPLEMENTED IN OPEN PECHA WEB PAGE

Frontend

  • In the Pecha Frontend repo which is made with vite (React) , there we use Vitest as the primary testing library.

  • Let’s go through a code snippet

mockAxios();
mockUseAuth()
mockReactQuery()
describe("UserRegistration Component", () => {

  const queryClient = new QueryClient();
  const setup = () => {
	render(
  	<Router>
    	<QueryClientProvider client={queryClient}>
      	<TolgeeProvider fallback={"Loading tolgee..."} tolgee={mockTolgee}>
        	<HomePage/>
      	</TolgeeProvider>
    	</QueryClientProvider>
  	</Router>
	);
  };
  beforeEach(() => {
	useQuery.mockImplementation(() => ({
  	data: {},
  	isLoading: false,
	}));
  });

  test("renders titles", () => {
	setup();
expect(screen.getByText("Browse the Library")).toBeInTheDocument();
	expect(screen.getByText("Explore Collections")).toBeInTheDocument();
	expect(screen.getByText("A Living Library of Buddhist Text")).toBeInTheDocument();
	expect(screen.getByText("Pecha connects users to Buddhist scriptures in various languages. Search a verse to explore its origins, interpretations, and related texts. Engage with the community by sharing insights and learning from others through sheets and topics.")).toBeInTheDocument();
	expect(screen.getByText("Learn More")).toBeInTheDocument();
  });

test("renders titles", () => {
  setup();
  expect(screen.getByText("Browse the Library")).toBeInTheDocument();
  expect(screen.getByText("Explore Collections")).toBeInTheDocument();
  expect(screen.getByText("A Living Library of Buddhist Text")).toBeInTheDocument();
  expect(screen.getByText("Pecha connects users to Buddhist scriptures...")).toBeInTheDocument();
  expect(screen.getByText("Learn More")).toBeInTheDocument();
});

  • Here this acts as the main test. So what are we testing here right? Here we are checking whether the screen that we render has the texts like “explore collections” and all. This makes the development so strict. Where each line of text is evaluated.


And here is how a coverage looks like:

  • Mostly in general a coverage above 80% is considered mergeable.

Backend:

  • In the Pecha backend server which is in Fast API, we use pytest library for unit testing and patch, MagicMock for mocking the external API call and database interaction.
  • The patch and the MagicMock come in the unittest.mock library.
  • Example of how the unit test is being done in the backend server:
def test_get_user_info_success():
    token = "valid_token"
    user = Users(
        firstname="John",
        lastname="Doe",
        username="johndoe",
        email="john.doe@example.com",
        title="Developer",
        organization="ExampleOrg",
        education="BSc, MSc",
        avatar_url="http://example.com/avatar.jpg",
        about_me="About John",
        social_media_accounts=[]
    )

    with patch("pecha_api.users.users_service.validate_token", return_value={"email": "john.doe@example.com"}), \
            patch("pecha_api.users.users_service.get_user_by_email", return_value=user):
        response = get_user_info(token)
        assert response.firstname == "John"
        assert response.lastname == "Doe"
        assert response.username == "johndoe"
        assert response.email == "john.doe@example.com"
  • Here we are testing whether the get_user_info_success() controller is working as expected or not.
  • So here we just created a dummy Users model instance with a dummy user details
  • Then the get_user_info(token) controller in invoke and the process begins as normally how it runs
  • But here we have mock some of the services such as validating the token since we are testing with dummy data we’re mocking the validate_token() controller.
  • And also we are mocking the get_user_by_email() controller to avoid interacting with the actual database since we already know what the database is going to return. We just return the user models as mentioned in the path() function.
  • After getting the response we assert or check against the expected response from out end such as since we expect the response firstname to be John we assert the response with
  • If the value turns out to be true then the test passes otherwise it will fail the test.

  • CODE COVERAGE:
    • Code coverage shows you how much of the code has been covered for the unit testing.
    • It let you know which part of code is not tested and might have a high chance of error rate in the production stage.
    • Generally it’s an ideal to keep the code coverage percentage more than 80%

Conclusion

  • Unit testing is vital in software development, ensuring code reliability, quality, and maintainability. By testing individual components, developers catch bugs early, refactor with confidence, and ensure new features don’t break existing functionality. It encourages cleaner, more modular code and provides valuable documentation.

  • With tools like Jest, Vitest, and Pytest, unit testing can be seamlessly integrated into both frontend and backend projects. Aiming for high code coverage helps minimize bugs in production and enhances the overall robustness of the application.

2 Likes