<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ E2ETesting - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ E2ETesting - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 24 Jun 2026 22:47:20 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/e2etesting/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Write Unit Tests and E2E Tests for NestJS Applications ]]>
                </title>
                <description>
                    <![CDATA[ Recently, I have been writing unit tests and E2E tests for a NestJS project. This was my first time writing tests for a backend project, and I found the process different from my experience with frontend testing, making it challenging to begin. After... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/nestjs-unit-testing-e2e-testing-guide/</link>
                <guid isPermaLink="false">67ffc1695667d9e59ef9bc46</guid>
                
                    <category>
                        <![CDATA[ nestjs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ E2ETesting ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gordan Tan ]]>
                </dc:creator>
                <pubDate>Wed, 16 Apr 2025 14:40:41 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744738441654/1bb2b329-d363-46d7-b091-e0e95ad22c9e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Recently, I have been writing unit tests and E2E tests for a NestJS project. This was my first time writing tests for a backend project, and I found the process different from my experience with frontend testing, making it challenging to begin.</p>
<p>After looking at some examples, I have gained a clearer understanding of how to approach testing. So I wrote an article to record and share my learning to help others who may be facing similar confusion.</p>
<p>In addition, I have put together a demo project with the relevant unit and E2E tests completed, which may be of interest. I’ve <a target="_blank" href="https://github.com/woai3c/nestjs-demo">uploaded the code to Github here</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-difference-between-unit-testing-and-e2e-testing">Difference Between Unit Testing and E2E Testing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-writing-unit-tests">Writing Unit Tests</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-first-test-case">The First Test Case</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-second-test-case">The Second Test Case</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-unit-test-coverage">Unit Test Coverage</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-writing-e2e-tests">Writing E2E Tests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-whether-to-write-tests">Whether to Write Tests</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-enhancing-system-robustness">Enhancing System Robustness</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-enhancing-maintainability">Enhancing Maintainability</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-enhancing-development-efficiency">Enhancing Development Efficiency</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-when-not-to-write-tests">When Not to Write Tests?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reference-materials">Reference Materials</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving into this tutorial, you should have:</p>
<ul>
<li><p>Basic knowledge of TypeScript and Node.js</p>
</li>
<li><p>Familiarity with NestJS fundamentals</p>
</li>
<li><p>Understanding of RESTful APIs</p>
</li>
<li><p>MongoDB installed (as the example uses MongoDB)</p>
</li>
<li><p>Node.js and npm/yarn installed on your system</p>
</li>
<li><p>Basic understanding of testing concepts</p>
</li>
</ul>
<p>You can find the complete code examples in the <a target="_blank" href="https://github.com/woai3c/nestjs-demo">demo repository</a>. You can clone it to follow along with the examples.</p>
<h2 id="heading-difference-between-unit-testing-and-e2e-testing">Difference Between Unit Testing and E2E Testing</h2>
<p>Unit tests and E2E tests are methods of software testing, but they have different goals and scopes.</p>
<p>Unit testing involves checking and verifying the smallest testable unit within the software. A function or a method, for example, can be considered a unit. In unit testing, you provide expected outputs for various inputs of a function and validate the correctness of its operation. The goal of unit testing is to quickly identify bugs within the function, and they are easy to write and execute rapidly.</p>
<p>On the other hand, E2E tests often simulate real-world user scenarios to test the entire application. For instance, the frontend typically uses a browser or headless browser for testing, while the backend does so by simulating API calls.</p>
<p>Within a NestJS project, unit tests might assess a specific service or a method of a controller, such as verifying if the <code>update</code> method in the Users module correctly updates a user. An E2E test, however, may examine a complete user journey, from creating a new user to updating their password and eventually deleting the user, which involves multiple services and controllers.</p>
<h2 id="heading-how-to-write-unit-tests">How to Write Unit Tests</h2>
<p>Writing unit tests for a utility function or method that doesn't involve interfaces is relatively straightforward. You only need to consider the various inputs and write the corresponding test code. But the situation becomes more complex once interfaces come into play. Let's use code as an example:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">async</span> validateUser(
  username: <span class="hljs-built_in">string</span>,
  password: <span class="hljs-built_in">string</span>,
): <span class="hljs-built_in">Promise</span>&lt;UserAccountDto&gt; {
  <span class="hljs-keyword">const</span> entity = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.usersService.findOne({ username });
  <span class="hljs-keyword">if</span> (!entity) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(<span class="hljs-string">'User not found'</span>);
  }
  <span class="hljs-keyword">if</span> (entity.lockUntil &amp;&amp; entity.lockUntil &gt; <span class="hljs-built_in">Date</span>.now()) {
    <span class="hljs-keyword">const</span> diffInSeconds = <span class="hljs-built_in">Math</span>.round((entity.lockUntil - <span class="hljs-built_in">Date</span>.now()) / <span class="hljs-number">1000</span>);
    <span class="hljs-keyword">let</span> message = <span class="hljs-string">`The account is locked. Please try again in <span class="hljs-subst">${diffInSeconds}</span> seconds.`</span>;
    <span class="hljs-keyword">if</span> (diffInSeconds &gt; <span class="hljs-number">60</span>) {
      <span class="hljs-keyword">const</span> diffInMinutes = <span class="hljs-built_in">Math</span>.round(diffInSeconds / <span class="hljs-number">60</span>);
      message = <span class="hljs-string">`The account is locked. Please try again in <span class="hljs-subst">${diffInMinutes}</span> minutes.`</span>;
    }
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(message);
  }
  <span class="hljs-keyword">const</span> passwordMatch = bcrypt.compareSync(password, entity.password);
  <span class="hljs-keyword">if</span> (!passwordMatch) {
    <span class="hljs-comment">// $inc update to increase failedLoginAttempts</span>
    <span class="hljs-keyword">const</span> update = {
      $inc: { failedLoginAttempts: <span class="hljs-number">1</span> },
    };
    <span class="hljs-comment">// lock account when the third try is failed</span>
    <span class="hljs-keyword">if</span> (entity.failedLoginAttempts + <span class="hljs-number">1</span> &gt;= <span class="hljs-number">3</span>) {
      <span class="hljs-comment">// $set update to lock the account for 5 minutes</span>
      update[<span class="hljs-string">'$set'</span>] = { lockUntil: <span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">5</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span> };
    }
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.usersService.update(entity._id, update);
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(<span class="hljs-string">'Invalid password'</span>);
  }
  <span class="hljs-comment">// if validation is sucessful, then reset failedLoginAttempts and lockUntil</span>
  <span class="hljs-keyword">if</span> (
    entity.failedLoginAttempts &gt; <span class="hljs-number">0</span> ||
    (entity.lockUntil &amp;&amp; entity.lockUntil &gt; <span class="hljs-built_in">Date</span>.now())
  ) {
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.usersService.update(entity._id, {
      $set: { failedLoginAttempts: <span class="hljs-number">0</span>, lockUntil: <span class="hljs-literal">null</span> },
    });
  }
  <span class="hljs-keyword">return</span> { userId: entity._id, username } <span class="hljs-keyword">as</span> UserAccountDto;
}
</code></pre>
<p>The code above is a method <code>validateUser</code> in the <code>auth.service.ts</code> file, primarily used to verify whether the username and password entered by the user during login are correct. It contains the following logic:</p>
<ol>
<li><p>Check if the user exists based on <code>username</code>. If not, throw a 401 exception (a 404 exception is also feasible).</p>
</li>
<li><p>See if the user is locked out. If so, throw a 401 exception with a relevant message.</p>
</li>
<li><p>Encrypt the <code>password</code> and compare it with the password in the database. If it's incorrect, throw a 401 exception (three consecutive failed login attempts will lock the account for 5 minutes).</p>
</li>
<li><p>If the login is successful, clear any previously failed login attempt counts (if applicable) and return the user <code>id</code> and <code>username</code> to the next stage.</p>
</li>
</ol>
<p>As you can see, the <code>validateUser</code> method includes four processing logics. So we need to write corresponding unit test code for these four points to ensure that the entire <code>validateUser</code> function is operating correctly.</p>
<h3 id="heading-the-first-test-case">The First Test Case</h3>
<p>When we start writing unit tests, we encounter a problem: the <code>findOne</code> method needs to interact with the database, and it looks for corresponding users in the database through <code>username</code>. But if every unit test has to interact with the database, the testing will become very cumbersome. So we can mock fake data to achieve this.</p>
<p>For example, assume we have registered a user named <code>woai3c</code>. Then, during login, the user data can be retrieved in the <code>validateUser</code> method through <code>const entity = await this.usersService.findOne({ username });</code>. As long as this line of code can return the desired data, there is no problem, even without database interaction. We can achieve this through mock data.</p>
<p>Now, let's look at the relevant test code for the <code>validateUser</code> method:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Test } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/testing'</span>;
<span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/modules/auth/auth.service'</span>;
<span class="hljs-keyword">import</span> { UsersService } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/modules/users/users.service'</span>;
<span class="hljs-keyword">import</span> { UnauthorizedException } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { TEST_USER_NAME, TEST_USER_PASSWORD } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tests/constants'</span>;
describe(<span class="hljs-string">'AuthService'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> authService: AuthService; <span class="hljs-comment">// Use the actual AuthService type</span>
  <span class="hljs-keyword">let</span> usersService: Partial&lt;Record&lt;keyof UsersService, jest.Mock&gt;&gt;;
  beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
    usersService = {
      findOne: jest.fn(),
    };
    <span class="hljs-keyword">const</span> <span class="hljs-keyword">module</span> = await Test.createTestingModule({
      providers: [        AuthService,
        {
          provide: UsersService,
          useValue: usersService,
        },
      ],
    }).compile();
    authService = <span class="hljs-built_in">module</span>.get&lt;AuthService&gt;(AuthService);
  });
  describe(<span class="hljs-string">'validateUser'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should throw an UnauthorizedException if user is not found'</span>, <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">await</span> expect(
        authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
      ).rejects.toThrow(UnauthorizedException);
    });
    <span class="hljs-comment">// other tests...</span>
  });
});
</code></pre>
<p>We get the user data by calling the <code>findOne</code> method of <code>usersService</code>, so we need to mock the <code>findOne</code> method of <code>usersService</code> in the test code:</p>
<pre><code class="lang-typescript">beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
    usersService = {
      findOne: jest.fn(), <span class="hljs-comment">// mock findOne method</span>
    };
    <span class="hljs-keyword">const</span> <span class="hljs-keyword">module</span> = await Test.createTestingModule({
      providers: [        AuthService, <span class="hljs-comment">// real AuthService, because we are testing its methods</span>
        {
          provide: UsersService, <span class="hljs-comment">// use mock usersService instead of real usersService</span>
          useValue: usersService,
        },
      ],
    }).compile();
    authService = <span class="hljs-built_in">module</span>.get&lt;AuthService&gt;(AuthService);
  });
</code></pre>
<p>We use <code>jest.fn()</code> to return a function to replace the real <code>usersService.findOne()</code>. If <code>usersService.findOne()</code> is called now, there will be no return value, so the first unit test case will pass:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should throw an UnauthorizedException if user is not found'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> expect(
    authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
  ).rejects.toThrow(UnauthorizedException);
});
</code></pre>
<p>Since <code>findOne</code> in <code>const entity = await this.usersService.findOne({ username });</code> of the <code>validateUser</code> method is a mocked fake function with no return value, the 2nd to 4th lines of code in the <code>validateUser</code> method could execute:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (!entity) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(<span class="hljs-string">'User not found'</span>);
}
</code></pre>
<p>It throws a 401 error, which is as expected.</p>
<h3 id="heading-the-second-test-case">The Second Test Case</h3>
<p>The second logic in the <code>validateUser</code> method is to determine if the user is locked, with the corresponding code as follows:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (entity.lockUntil &amp;&amp; entity.lockUntil &gt; <span class="hljs-built_in">Date</span>.now()) {
  <span class="hljs-keyword">const</span> diffInSeconds = <span class="hljs-built_in">Math</span>.round((entity.lockUntil - <span class="hljs-built_in">Date</span>.now()) / <span class="hljs-number">1000</span>);
  <span class="hljs-keyword">let</span> message = <span class="hljs-string">`The account is locked. Please try again in <span class="hljs-subst">${diffInSeconds}</span> seconds.`</span>;
  <span class="hljs-keyword">if</span> (diffInSeconds &gt; <span class="hljs-number">60</span>) {
    <span class="hljs-keyword">const</span> diffInMinutes = <span class="hljs-built_in">Math</span>.round(diffInSeconds / <span class="hljs-number">60</span>);
    message = <span class="hljs-string">`The account is locked. Please try again in <span class="hljs-subst">${diffInMinutes}</span> minutes.`</span>;
  }
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(message);
}
</code></pre>
<p>As you can see, we can determine that the current account is locked if there is a lock time <code>lockUntil</code> in the user data and the lock end time is greater than the current time. So we need to mock a user data with the <code>lockUntil</code> field:</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should throw an UnauthorizedException if the account is locked'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> lockedUser = {
    _id: TEST_USER_ID,
    username: TEST_USER_NAME,
    password: TEST_USER_PASSWORD,
    lockUntil: <span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">5</span>, <span class="hljs-comment">// The account is locked for 5 minutes</span>
  };
  usersService.findOne.mockResolvedValueOnce(lockedUser);
  <span class="hljs-keyword">await</span> expect(
    authService.validateUser(TEST_USER_NAME, TEST_USER_PASSWORD),
  ).rejects.toThrow(UnauthorizedException);
});
</code></pre>
<p>In the test code above, an object <code>lockedUser</code> is first defined, which contains the <code>lockUntil</code> field we need. Then, it is used as the return value for <code>findOne</code>, achieved by <code>usersService.findOne.mockResolvedValueOnce(lockedUser);</code>. Thus, when the <code>validateUser</code> method is executed, the user data within it is the mocked data, successfully allowing the second test case to pass.</p>
<h3 id="heading-unit-test-coverage">Unit Test Coverage</h3>
<p>Unit test coverage (Code Coverage) is a metric used to describe how much of the application code has been covered or tested by unit tests. It is typically expressed as a percentage, indicating how much of all possible code paths have been covered by test cases.</p>
<p>Unit test coverage usually includes the following types:</p>
<ul>
<li><p>Line Coverage: How many lines of code are covered by the tests.</p>
</li>
<li><p>Function Coverage: How many functions or methods are covered by the tests.</p>
</li>
<li><p>Branch Coverage: How many code branches are covered by the tests (for example, <code>if/else</code> statements).</p>
</li>
<li><p>Statement Coverage: How many statements in the code are covered by the tests.</p>
</li>
</ul>
<p>Unit test coverage is an important metric to measure the quality of unit tests, but it is not the only metric. A high coverage rate can help to detect errors in the code, but it does not guarantee the quality of the code. A low coverage rate may mean that there is untested code, potentially with undetected errors.</p>
<p>The image below shows the unit test coverage results for a demo project:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/e3de4ecc6be093ac92a514fa183f688c455b00cc15a3d003bfe2f25e31a08c4f/68747470733a2f2f6d69726f2e6d656469756d2e636f6d2f76322f726573697a653a6669743a313331302f666f726d61743a776562702f302a515a5f4d4a77774c715752314d3136652e706e67"><img src="https://camo.githubusercontent.com/e3de4ecc6be093ac92a514fa183f688c455b00cc15a3d003bfe2f25e31a08c4f/68747470733a2f2f6d69726f2e6d656469756d2e636f6d2f76322f726573697a653a6669743a313331302f666f726d61743a776562702f302a515a5f4d4a77774c715752314d3136652e706e67" alt="Unit test coverage overview showing test results with percentages for statements, branches, functions, and lines" width="600" height="400" loading="lazy"></a></p>
<p>For files like services and controllers, it's generally better to have a higher unit test coverage, while for files like modules there's no need to write unit tests – nor is it possible to write them, as it's meaningless.</p>
<p>This is because NestJS modules are primarily configuration files that define the structure of your application by connecting controllers, services, and other components together. They don't contain actual business logic to test, but rather serve as wiring instructions for the dependency injection system. Testing modules would only verify that NestJS's core functionality works correctly, which is already tested by the NestJS team themselves.</p>
<p>The above image represents the overall metrics for the entire unit test coverage. If you want to view the test coverage for a specific function, you can open the <code>coverage/lcov-report/index.html</code> file in the project's root directory. For example, I want to see the specific test situation for the <code>validateUser</code> method:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/e5757001ae5bfec61c2b3ed19f7ef99cffc6c014480d7dad17ab28a2713f6aa0/68747470733a2f2f6d69726f2e6d656469756d2e636f6d2f76322f726573697a653a6669743a313430302f666f726d61743a776562702f302a4e32542d44694d754566776b332d33322e706e67"><img src="https://camo.githubusercontent.com/e5757001ae5bfec61c2b3ed19f7ef99cffc6c014480d7dad17ab28a2713f6aa0/68747470733a2f2f6d69726f2e6d656469756d2e636f6d2f76322f726573697a653a6669743a313430302f666f726d61743a776562702f302a4e32542d44694d754566776b332d33322e706e67" alt="Detailed test coverage for the validateUser method showing specific uncovered lines highlighted in red" width="600" height="400" loading="lazy"></a></p>
<p>As you can see, the original unit test coverage for the <code>validateUser</code> method is not 100%, and there are still two lines of code that were not executed. But it doesn't matter much, as it does not affect the four key processing nodes, and we shouldn’t pursue high test coverage unidimensionally.</p>
<h2 id="heading-how-to-write-e2e-tests">How to Write E2E Tests</h2>
<p>In the unit tests section, you learned how to write unit tests for each feature of the <code>validateUser()</code> function, using mocked data to ensure that each feature could be tested.</p>
<p>In e2E testing, we need to simulate real user scenarios, so connecting to a database for testing is necessary. So, the methods in the <code>auth.service.ts</code> module that we'll be testing all interact with the database.</p>
<p>The <code>auth</code> module primarily includes the following features:</p>
<ul>
<li><p>Registration</p>
</li>
<li><p>Login</p>
</li>
<li><p>Token refresh</p>
</li>
<li><p>Reading user information</p>
</li>
<li><p>Changing password</p>
</li>
<li><p>Deleting users</p>
</li>
</ul>
<p>E2E tests need to test these six features one by one, starting with <code>registration</code> and ending with <code>deleting users</code>. During testing, we can create a dedicated test user to conduct the tests and then delete this test user upon completion, so we don’t leave any unnecessary information in the test database.</p>
<pre><code class="lang-typescript">beforeAll(<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> moduleFixture: TestingModule = <span class="hljs-keyword">await</span> Test.createTestingModule({
    imports: [AppModule],
  }).compile()
  app = moduleFixture.createNestApplication()
  <span class="hljs-keyword">await</span> app.init()
  <span class="hljs-comment">// Perform a login to obtain a token</span>
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> request(app.getHttpServer())
    .post(<span class="hljs-string">'/auth/register'</span>)
    .send({ username: TEST_USER_NAME, password: TEST_USER_PASSWORD })
    .expect(<span class="hljs-number">201</span>)
  accessToken = response.body.access_token
  refreshToken = response.body.refresh_token
})
afterAll(<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> request(app.getHttpServer())
    .delete(<span class="hljs-string">'/auth/delete-user'</span>)
    .set(<span class="hljs-string">'Authorization'</span>, <span class="hljs-string">`Bearer <span class="hljs-subst">${accessToken}</span>`</span>)
    .expect(<span class="hljs-number">200</span>)
  <span class="hljs-keyword">await</span> app.close()
})
</code></pre>
<p>The <code>beforeAll</code> hook function runs before all tests begin, so we can register a test account <code>TEST_USER_NAME</code> here. The <code>afterAll</code> hook function runs after all tests end, so it's suitable to delete the test account <code>TEST_USER_NAME</code> here. It also conveniently tests the registration and deletion functions.</p>
<p>In the previous section's unit tests, we wrote relevant unit tests around the <code>validateUser</code> method. Actually, this method is executed during login to validate if the user's account and password are correct. So this E2E test will also use the login process to demonstrate how to compose the E2E test cases.</p>
<p>The entire login test process includes five small tests:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'login'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'/auth/login (POST)'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// ...</span>
    })
    it(<span class="hljs-string">'/auth/login (POST) with user not found'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// ...</span>
    })
    it(<span class="hljs-string">'/auth/login (POST) without username or password'</span>, <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-comment">// ...</span>
    })
    it(<span class="hljs-string">'/auth/login (POST) with invalid password'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// ...</span>
    })
    it(<span class="hljs-string">'/auth/login (POST) account lock after multiple failed attempts'</span>, <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-comment">// ...</span>
    })
  })
</code></pre>
<p>These five tests are as follows:</p>
<ol>
<li><p>Successful login, return 200</p>
</li>
<li><p>If the user does not exist, throw a 401 exception</p>
</li>
<li><p>If password or username is not provided, throw a 400 exception</p>
</li>
<li><p>Login with the wrong password, throw a 401 exception</p>
</li>
<li><p>If the account is locked, throw a 401 exception</p>
</li>
</ol>
<p>Now let's start writing the E2E tests:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// login success</span>
it(<span class="hljs-string">'/auth/login (POST)'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> request(app.getHttpServer())
    .post(<span class="hljs-string">'/auth/login'</span>)
    .send({ username: TEST_USER_NAME, password: TEST_USER_PASSWORD })
    .expect(<span class="hljs-number">200</span>)
})
<span class="hljs-comment">// if user not found, should throw 401 exception</span>
it(<span class="hljs-string">'/auth/login (POST) with user not found'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> request(app.getHttpServer())
    .post(<span class="hljs-string">'/auth/login'</span>)
    .send({ username: TEST_USER_NAME2, password: TEST_USER_PASSWORD })
    .expect(<span class="hljs-number">401</span>) <span class="hljs-comment">// Expect an unauthorized error</span>
})
</code></pre>
<p>Writing E2E test code is relatively straightforward: you simply call the interface and then verify the result. For example, for the successful login test, we just need to verify that the returned result is 200.</p>
<p>The first four tests are quite simple. Now let's look at a slightly more complicated E2E test, which is to verify whether an account is locked.</p>
<pre><code class="lang-typescript">it(<span class="hljs-string">'/auth/login (POST) account lock after multiple failed attempts'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> moduleFixture: TestingModule = <span class="hljs-keyword">await</span> Test.createTestingModule({
    imports: [AppModule],
  }).compile()
  <span class="hljs-keyword">const</span> app = moduleFixture.createNestApplication()
  <span class="hljs-keyword">await</span> app.init()
  <span class="hljs-keyword">const</span> registerResponse = <span class="hljs-keyword">await</span> request(app.getHttpServer())
    .post(<span class="hljs-string">'/auth/register'</span>)
    .send({ username: TEST_USER_NAME2, password: TEST_USER_PASSWORD })
  <span class="hljs-keyword">const</span> accessToken = registerResponse.body.access_token
  <span class="hljs-keyword">const</span> maxLoginAttempts = <span class="hljs-number">3</span> <span class="hljs-comment">// lock user when the third try is failed</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; maxLoginAttempts; i++) {
    <span class="hljs-keyword">await</span> request(app.getHttpServer())
      .post(<span class="hljs-string">'/auth/login'</span>)
      .send({ username: TEST_USER_NAME2, password: <span class="hljs-string">'InvalidPassword'</span> })
  }
  <span class="hljs-comment">// The account is locked after the third failed login attempt</span>
  <span class="hljs-keyword">await</span> request(app.getHttpServer())
    .post(<span class="hljs-string">'/auth/login'</span>)
    .send({ username: TEST_USER_NAME2, password: TEST_USER_PASSWORD })
    .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
      expect(res.body.message).toContain(
        <span class="hljs-string">'The account is locked. Please try again in 5 minutes.'</span>,
      )
    })
  <span class="hljs-keyword">await</span> request(app.getHttpServer())
    .delete(<span class="hljs-string">'/auth/delete-user'</span>)
    .set(<span class="hljs-string">'Authorization'</span>, <span class="hljs-string">`Bearer <span class="hljs-subst">${accessToken}</span>`</span>)
  <span class="hljs-keyword">await</span> app.close()
})
</code></pre>
<p>When a user fails to log in three times in a row, the account will be locked. So in this test, we cannot use the test account <code>TEST_USER_NAME</code>, because if the test is successful, this account will be locked and unable to continue the following tests. We need to register another new user <code>TEST_USER_NAME2</code> specifically to test account locking, and delete this user after the test is successful.</p>
<p>So, as you can see, the code for this E2E test is quite substantial, requiring a lot of setup and teardown work, but the actual test code is just these few lines:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// login three times</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; maxLoginAttempts; i++) {
  <span class="hljs-keyword">await</span> request(app.getHttpServer())
    .post(<span class="hljs-string">'/auth/login'</span>)
    .send({ username: TEST_USER_NAME2, password: <span class="hljs-string">'InvalidPassword'</span> })
}
<span class="hljs-comment">// test if the account is locked</span>
<span class="hljs-keyword">await</span> request(app.getHttpServer())
  .post(<span class="hljs-string">'/auth/login'</span>)
  .send({ username: TEST_USER_NAME2, password: TEST_USER_PASSWORD })
  .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
    expect(res.body.message).toContain(
      <span class="hljs-string">'The account is locked. Please try again in 5 minutes.'</span>,
    )
  })
</code></pre>
<p>Writing E2E test code is relatively simple. You don't need to consider mock data or test coverage. It's sufficient if the entire system process runs as expected.</p>
<h2 id="heading-when-to-write-tests">When to Write Tests</h2>
<p>If possible, I generally recommend writing tests. Doing so can enhance the robustness, maintainability, and development efficiency of the system. Here are some key reasons why writing tests is useful:</p>
<h3 id="heading-enhancing-system-robustness">Enhancing System Robustness</h3>
<p>When writing code, you usually focus on the program flow under normal inputs to ensure the core functionality works properly. But you might often overlook some edge cases, such as abnormal inputs.</p>
<p>Writing tests changes this, as it forces you to consider how to handle these cases and respond appropriately, thus preventing crashes. So we can say that writing tests indirectly improves system robustness.</p>
<h3 id="heading-enhancing-maintainability">Enhancing Maintainability</h3>
<p>Taking over a new project that includes comprehensive tests can be very pleasant. They act as a guide, helping you quickly understand the various functionalities. Just by looking at the test code, you can easily grasp the expected behavior and boundary conditions of each function without having to go through each line of the function's code.</p>
<h3 id="heading-enhancing-development-efficiency">Enhancing Development Efficiency</h3>
<p>Imagine a project that hasn't been updated for a while suddenly receives new requirements. After making changes, you might worry about introducing bugs. Without tests, you would need to manually test the entire project again — wasting time and being inefficient.</p>
<p>With complete tests, a single command can tell you whether the code changes have impacted existing functionalities. Even if there are errors, you can quickly locate them and address them.</p>
<h2 id="heading-when-not-to-write-tests">When Not to Write Tests</h2>
<p>For short-term projects and projects with very fast requirement iterations, it's not recommended to write tests.</p>
<p>For example, a project built for an event that will be useless after the event ends doesn't need tests. Also, for projects that undergo very fast requirement iterations, writing tests could enhance development efficiency, but that's based on the premise that function iterations are slow. If the function you just completed changes in a day or two, the related test code must be rewritten.</p>
<p>So, it's better not to write tests at all in these cases and rely on the testing team instead – because writing tests is very time-consuming and not worth the effort for these situations.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I’ve explained in detail how to write unit tests and E2E tests for NestJS projects. But I still want to reiterate the importance of testing. It can enhance the robustness, maintainability, and development efficiency of the system.</p>
<p>If you don't have the opportunity to write tests, I suggest you start a practice project yourself or participate in some open-source projects and contribute code to them. Open-source projects generally have stricter code requirements. Contributing code may require you to write new test cases or modify existing ones.</p>
<h3 id="heading-reference-materials">Reference Materials</h3>
<ul>
<li><p><a target="_blank" href="https://nestjs.com/">NestJS</a>: A framework for building efficient, scalable Node.js server-side applications.</p>
</li>
<li><p><a target="_blank" href="https://www.mongodb.com/">MongoDB</a>: A NoSQL database used for data storage.</p>
</li>
<li><p><a target="_blank" href="https://jestjs.io/">Jest</a>: A testing framework for JavaScript and TypeScript.</p>
</li>
<li><p><a target="_blank" href="https://github.com/visionmedia/supertest">Supertest</a>: A library for testing HTTP servers.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
