Search code examples
angularunit-testingxmlhttprequestkarma-jasminewebpack-dev-server

Is there a simple or idiomatic way to access a file in an Angular unit test?


I have the default setup for unit tests (Jasmine and Karma). I need to write a unit test for a function that reads a provided Element node from an XMLDocument. While I could generate the DOMs programatically, I already have example XML files in the asset folder and it would be easiest and most realistic to parse those the same way I do in the actual app. However, providing the files to the unit tests seems to be very difficult. Since the thing I'm trying to accomplish is basic and probably done a lot, I'm hoping there might be a simple solution I'm looking over.

I know I can copy-and-paste the XML source into a string literal, but the files I want to use are 20-50 kB and contain quotes, so I'm trying to avoid that. From what I've read online such as this question, the way to import files is with raw-loader. However, that takes a lot of work to setup and that method is now out of date in favor of asset modules. Angular's HttpClientTestingModule doesn't help me because it requires I already have the string in memory, which defeats the purpose of loading an asset with it.


Solution

  • As it turns out, the solution can be almost as simple as installing an npm package and then following MGX's answer.

    Declaring and Importing

    Assuming the default configuration for Angular CLI 17, to declare a module, just add a file with any name ending with a .d.ts in the /src/ directory. You then use declare module 'expression', where 'expression' seems to follow a regex-like syntax. '*.xml' is all you need for it to automatically apply to all xml files, while you can also make it more specific. I ended up with this:

    declare module '*.xml' {
      const value: string;
      export default value;
    }
    

    As MGX said, you then need to import the file you want to read, not the .d.ts file.

    Configuring raw-loading

    If you just do that, it still won't work. You will get an error:

    Error: Module parse failed: Unexpected token (1:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

    According to the docs, the fix for this involves overriding the default settings for webpack, either with something like this:

    module: {
      rules: [{ test: /\.xml$/, use: 'raw-loader' }],
    },
    

    or like this:

    module: {
      rules: [{ test: /\.xml/, type: 'asset/source' }],
    },
    

    However, manually fiddling with the the webpack config will mess up Angular CLI's scripts. Many, but not all, of the ways said to edit the config are out of date. Fortunately, Angular Builders, a tool for making custom build scripts with Angular, is actively maintained.

    Instead of trying that first, I tried the package raw-loader. It has been archived for more than three years, but it worked for me in Angular 17.

    I just had to install it:

    npm install raw-loader --save-dev
    

    and edit my import statement slightly:

    import XML from 'raw-loader!file.xml';
    

    and then I got the contents of the file as a string in XML.

    Parsing the String

    To actually use the XML data, I still had to parse the string, but that is relatively trivial. Since Angular is a web framework, I used the browser's api to parse the string into an XMLDocument, similar to a web pages DOM representation.

    const parser = new DOMParser();
    const xmlDoc: XMLDocument = parser.parseFromString(XML, "text/xml");
    

    For more information, check out the w3schools guide on XML.

    For those who don't reliably have that API, or who think parsing to a DOM is overkill, look into xml2js or fast-xml-parser.