{"id":15174,"date":"2024-01-18T00:00:00","date_gmt":"2024-01-18T08:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/ise\/?p=15174"},"modified":"2024-11-03T23:44:32","modified_gmt":"2024-11-04T07:44:32","slug":"jest-mocking-best-practices","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/jest-mocking-best-practices\/","title":{"rendered":"Jest Mocking Best Practices"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>In software engineering, unit testing is crucial to the functionality, maintainability, and reliability of the code base.\nBy isolating the smallest testable unit (a function, class, etc.) and abstracting away its dependencies, it is possible to validate the robustness and accuracy of each unit.<\/p>\n<p>In order to focus on the unit under test itself, we must often mock its dependencies.\nThis way we can simplify our tests and force our unit under test to flow through different logical branches of the code &#8211; all without relying on any external systems or potentially changing implementations.<\/p>\n<p><a href=\"https:\/\/jestjs.io\/\">Jest<\/a> is one of the most common testing frameworks for testing JavaScript Code.\n<a href=\"https:\/\/www.npmjs.com\/package\/ts-jest\">TS-Jest<\/a> provides seamless integration between TypeScript (TS) and Jest, making it simple to write tests in TS.\nNot only does Jest now integrate with TS, it also has fully fledged mocking functionality that allows us to write high quality unit tests.<\/p>\n<p>In this blog post, we walk through samples of how to utilize mocking in different unit testing scenarios. The code for these samples can also be found in <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/tree\/main\">this GitHub repository<\/a>.<\/p>\n<h2>Mocking Instances of Classes<\/h2>\n<p>Very often, the class under test depends on instances of other classes.\nTypically, in following the <a href=\"https:\/\/stackify.com\/dependency-inversion-principle\/\">dependency inversion principle<\/a>, individual classes should depend on interfaces &#8212; the implementation of which are often passed in through the class&#8217;s constructor.\nThis gives us much flexibility in testing, allowing us to simply define the mocks that implement the required interfaces and pass them through the constructor of a class when writing its unit tests.<\/p>\n<p>Let&#8217;s use the following <code>Introducer<\/code> class (from <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/tree\/main\/1-Mocking-Instances-Of-Classes\">this section of the repo<\/a>) as an example:<\/p>\n<pre><code class=\"language-ts\">export class Introducer {\r\n    constructor(private personClient: IPersonClient) {\r\n    }\r\n\r\n    helloPerson(id: string): string {\r\n        return `Hello ${this.personClient.getName(id)}!`;\r\n    }\r\n\r\n    askAQuestion(id: string): string {\r\n        const age = this.personClient.getAge(id);\r\n        const name = this.personClient.getName(id);\r\n        return age &gt; 22 ? `What do you do for a living ${name}?` : `What's your favorite hobby ${name}?`;\r\n    }\r\n}<\/code><\/pre>\n<p>Since this <code>Introducer<\/code> class depends on an <code>IPersonClient<\/code> instance and because we don&#8217;t want to use the real <code>Person API<\/code> in our unit tests, we create a <code>mockPersonClient<\/code>.<\/p>\n<pre><code class=\"language-ts\">const mockPersonClient = {\r\n    getName: mockGetName,\r\n    getAge: mockGetAge\r\n};<\/code><\/pre>\n<p>The underlying <code>IPersonClient<\/code> methods are mocked outside of the <code>mockPersonClient<\/code> object so that additional assertions can be made around the context of these functions (how many times they were called and with what arguments).\nThis is done via the <a href=\"https:\/\/jestjs.io\/docs\/mock-function-api#mockfnmockimplementationfn\"><code>mockImplementation<\/code> method<\/a> available on the Jest mock created with the <a href=\"https:\/\/jestjs.io\/docs\/jest-object#jestfnimplementation\"><code>jest.fn()<\/code> function<\/a>:<\/p>\n<pre><code class=\"language-ts\">const mockGetName = jest.fn().mockImplementation((id): string =&gt; {\r\n  return people[id].name;\r\n});\r\nconst mockGetAge = jest.fn().mockImplementation((id): number =&gt; {\r\n  return people[id].age;\r\n});<\/code><\/pre>\n<pre><code class=\"language-ts\">expect(mockGetName).toBeCalledTimes(1);\r\nexpect(mockGetAge).toBeCalledTimes(1);<\/code><\/pre>\n<p>Now, at the beginning of each test, we can create an <code>Introducer<\/code> instance with our mock <code>IPersonClient<\/code>:<\/p>\n<pre><code class=\"language-ts\">const introducer = new Introducer(mockPersonClient);<\/code><\/pre>\n<p>We should also clear the mocks after (or before) each test is run to make sure there is no carryover of information or state between tests.\nThis is a best practice that should be kept in mind when working with mocks in Jest.<\/p>\n<pre><code class=\"language-ts\">afterEach(()=&gt; {\r\n    jest.clearAllMocks();\r\n})<\/code><\/pre>\n<p>Moreover, with all the mocks in place, we now can write tests for the <code>Introducer<\/code> public methods.\nThe full tests can be found in this <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/blob\/main\/1-Mocking-Instances-Of-Classes\/introducer.test.ts\">test file<\/a><\/p>\n<blockquote><p>Note: To check out the full code\/test sample, see the <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/blob\/main\/1-Mocking-Instances-Of-Classes\/README.md\">repo<\/a><\/p><\/blockquote>\n<h2>Mocking Static Functions<\/h2>\n<p>Like with other dependencies in unit testing, we mock static functions so that we can focus on testing the unit&#8217;s logic in isolation.\nThis ensures that the testing process is reliable and efficient.\nMocking static functions also allows us to control the behavior and responses of the static function during testing, so we can create deterministic tests that have predictable outcomes.<\/p>\n<p>One common scenario where we may need to mock a static function is when following the <a href=\"https:\/\/sbcode.net\/typescript\/factory\/\">Factory Design Pattern<\/a> to create a new object as follows:<\/p>\n<pre><code class=\"language-ts\">export class PersonFactory {\r\n  private static _instance: IPerson;\r\n\r\n  static getInstance(name: string, age: number): IPerson {\r\n    if (!PersonFactory._instance) {\r\n      const persona = new Person(name, age);\r\n      PersonFactory._instance = persona;\r\n    }\r\n\r\n    return PersonFactory._instance;\r\n  }\r\n}<\/code><\/pre>\n<p>In this case, we want to mock our static <code>getInstance<\/code> function within our Factory class.\nWe use the <a href=\"https:\/\/jestjs.io\/docs\/jest-object#jestspyonobject-methodname\"><code>jest.spyon()<\/code><\/a> function and the <code>.mockReturnValue()<\/code> method to overwrite our original Factory method and return a mocked object.<\/p>\n<p>Let&#8217;s take an example, which is detailed further in <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/tree\/main\/2-Static-Functions\">this section of the repo<\/a>:<\/p>\n<p>Say, we want to test a method that gets a new Person object from a <code>getInstance<\/code> method in a PersonFactory and then returns a message that says hello to that person.\nTo test this function, as our unit under test, we must first mock our returned person object.<\/p>\n<pre><code class=\"language-ts\">const mockAnnika = {\r\n    nickname: 'Annika',\r\n    age: 22\r\n};<\/code><\/pre>\n<p>Then we can use the <code>jest.spyon()<\/code> function on the PersonFactory so that the class&#8217;s <code>getInstance<\/code> method will be overwritten to return our mocked person object.<\/p>\n<pre><code class=\"language-ts\">jest.spyOn(PersonFactory, 'getInstance').mockReturnValue(mockAnnika);<\/code><\/pre>\n<p>Finally, we can call our function under test and expect the returned message to be a simple hello to our mocked Person.<\/p>\n<pre><code class=\"language-ts\">const returnedHelloAnnika = HelloPerson.sayHelloAnnika();\r\nexpect(helloAnnika).toEqual(\"Hello, \" + `${mockAnnika.nickname}!`);<\/code><\/pre>\n<blockquote><p>Note: To check out the full code\/test sample, see the <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/blob\/main\/2-Static-Functions\/README.md\">repo<\/a><\/p><\/blockquote>\n<h2>Mocking with Environment Variables<\/h2>\n<p>Environment variables are very useful in software engineering for many reasons including application configuration.\nMany classes require these configurations to determine their behavior.\nWhile there is a preference for reading these environment variables in at the app entry point and passing config objects to classes (making testing very simple), there are times when this may not be possible.\nIn this other case, where you must access the environment variables directly (using <code>process.env.YOUR_ENVIRONMENT_VAR<\/code>), there are a few things to consider during testing.<\/p>\n<p>If your tests are simple and every test file can use the same exact set of environment variables, utilize the <a href=\"https:\/\/jestjs.io\/docs\/configuration#setupfiles-array\"><code>setupFiles<\/code><\/a> field of the <code>jest.config.js<\/code> to point to a TS file that will be run before each test file is executed (where you can set your environment variables) as shown <a href=\"https:\/\/medium.com\/swlh\/how-to-setup-dotenv-globally-with-jest-testing-in-depth-explanation-323fad824485\">here<\/a>.<\/p>\n<p>However, in most cases, where different environment variables or the flexibility to change environment variables on a per test basis is needed, follow the sample scenario described below:<\/p>\n<p>For this scenario, we have a simple (but admittedly useless) <code>LoggingService<\/code> that returns different strings based on which environment it is in (Azure vs Local):<\/p>\n<pre><code class=\"language-ts\">export class LoggingService {\r\n    constructor() {\r\n    }\r\n\r\n    \/\/ returns a string just for example sake\r\n    info(message: string): string {\r\n        const loggingEnvironment = process.env.LOGGING_ENVIRONMENT;\r\n\r\n        if (loggingEnvironment == \"AZURE\") {\r\n            return \"[AZURE] \" + message;\r\n        } else  {\r\n            return \"[LOCAL] \" + message;\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>We must unit test to see whether the <code>LoggingService<\/code> returns the right string based on the environment variables set.<\/p>\n<p>Because these tests will change environment variables, we first cache the old environment in the <code>beforeAll<\/code> block and reset this exact environment after all the tests in the <code>afterAll<\/code> block.\nBefore each test, we importantly utilize the <a href=\"https:\/\/jestjs.io\/docs\/jest-object#jestresetmodules\"><code>jest.resetModules()<\/code><\/a> function so that modules are imported again in between each test.\nThis is especially important if the class being tested captures environment variables globally (in its file but outside the class definition).\nLastly, because the <code>LoggingService<\/code> code and what we&#8217;re testing is so basic, at the beginning of each test, we simply set the required environment variable and test the output of the <code>info(message: string)<\/code> function based on that environment variable:<\/p>\n<pre><code class=\"language-ts\">process.env.LOGGING_ENVIRONMENT = \"AZURE\";\r\nconst message = \"message\";\r\nexpect(loggingService.info(message)).toBe(\"[AZURE] \" + message);<\/code><\/pre>\n<blockquote><p>Note: if the entire test file requires the same set of environment variables, you can set them in the <code>beforeAll<\/code> via <code>process.env.______<\/code> or add them to a <code>test.env<\/code> file and load them using a library like <a href=\"https:\/\/www.npmjs.com\/package\/dotenv\">dotenv<\/a>:<\/p><\/blockquote>\n<pre><code class=\"language-ts\">beforeAll(() =&gt; {\r\n    \/\/ cache a copy of user env before tests\r\n    cachedEnv = { ...process.env };\r\n    \/\/ add 'test.env' values to user env\r\n    dotenv.config({ override: true, path: path.resolve(__dirname, 'test.env') });\r\n});\r\n\r\nbeforeEach(() =&gt; {\r\n    \/\/ Resets the module registry - the cache of all required modules.\r\n    \/\/ This is useful to isolate modules where local state might conflict between tests.\r\n    jest.resetModules();\r\n\r\n    \/\/ Create logging service for tests\r\n    loggingService = new LoggingService();\r\n})\r\n\r\nafterAll(() =&gt; {\r\n    \/\/ reset user env vars\r\n    process.env = cachedEnv;\r\n});<\/code><\/pre>\n<p><code>test.env<\/code>:<\/p>\n<pre><code class=\"language-env\">LOGGING_ENVIRONMENT=LOCAL<\/code><\/pre>\n<blockquote><p>Note: To check out the full code\/test sample, see the <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/blob\/main\/3-Testing-With-Environment-Vars\/README.md\">repo<\/a><\/p><\/blockquote>\n<h2>Mocking NPM Libraries<\/h2>\n<p>Sometimes there are instances where you cannot constructor inject a dependency and you rely on constructing a dependency in your code itself.\nWhile this makes testing more difficult, Jest has a workaround.\nThis section shows how to mock classes from npm packages that are constructed in code.\nBy using the <code>jest.mock()<\/code> method, we can specify the library to mock and add the list of dependencies desired, including the classes that need to be constructed.<\/p>\n<p>As an example, let&#8217;s assume we have an HTTP-triggered Typescript Azure Function that deletes entities from Azure Table Storage.\nBecause TypeScript Azure Functions doesn&#8217;t come with a Dependency Injection framework out of the box, in our basic Azure Function we construct a <code>TableClient<\/code> inline:<\/p>\n<pre><code class=\"language-ts\">const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise&lt;void&gt; {\r\n  ...\r\n  const tableClient = new TableClient(\r\n    config.STORAGE_ENDPOINT,\r\n    config.STORAGE_TABLE_NAME,\r\n    new AzureNamedKeyCredential(config.STORAGE_ACCOUNT_NAME, config.STORAGE_ACCOUNT_KEY),\r\n  );\r\n  try {\r\n    await tableClient.deleteEntity(entityName, rowKey);\r\n    ...\r\n  } catch {\r\n    ...\r\n  }\r\n  ...\r\n};<\/code><\/pre>\n<p>In order to mock this class (as well as the <code>AzureNamedKeyCredential<\/code> required in its constructor) from the <code>@azure\/data-tables<\/code> library, we set up the mock of <code>TableClient<\/code>\nand <code>AzureNamedKeyCredential<\/code> at the top of the test file:<\/p>\n<pre><code class=\"language-ts\">const mockedDeleteEntity = jest.fn();\r\n\r\njest.mock('@azure\/data-tables', () =&gt; ({\r\n  \/\/ use \"function\" syntax since they need to be instantiated\r\n  AzureNamedKeyCredential: function () {\r\n    return 'test credential';\r\n  },\r\n  TableClient: function () {\r\n    return {\r\n      deleteEntity: mockedDeleteEntity,\r\n    };\r\n  },\r\n}));<\/code><\/pre>\n<p>We can also make assertions on how many times the <code>deleteEntity<\/code> function is called on the <code>TableClient<\/code> by making its implementation a Jest function (<code>jest.fn()<\/code>):<\/p>\n<pre><code class=\"language-ts\">expect(mockedDeleteEntity).toBeCalled();<\/code><\/pre>\n<blockquote><p>Note: To check out the full code\/test sample, see the <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/blob\/main\/4-Mocking-NPM-Libraries\/README.md\">repo<\/a><\/p><\/blockquote>\n<h2>Conclusion<\/h2>\n<p>Overall, utilizing Jest&#8217;s mocking features can make unit testing TypeScript applications easy.\nIn this blog post, we have detailed a few common scenarios that we have come across in our team&#8217;s work, but this is certainly not an exhaustive list.\nRefer to the <a href=\"https:\/\/jestjs.io\/docs\/mock-functions\">Jest Mocking Documentation<\/a> for additional information on mocking with Jest.<\/p>\n<blockquote><p>Note: Please see the <a href=\"https:\/\/github.com\/abassey2\/JestMockingSharing\/tree\/main\">full repo<\/a> for further details and instructions on how to run these outlined tests.<\/p><\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Best practices and common mocking scenarios for using Jest to unit test TypeScript applications.<\/p>\n","protected":false},"author":118441,"featured_media":15175,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,3451],"tags":[3484,3302,367],"class_list":["post-15174","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cse","category-ise","tag-jest","tag-typescript","tag-unit-testing"],"acf":[],"blog_post_summary":"<p>Best practices and common mocking scenarios for using Jest to unit test TypeScript applications.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/15174","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/118441"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=15174"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/15174\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/15175"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=15174"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=15174"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=15174"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}