We recently switched our many in house Angular repos to a Monorepo. There are tons of benefits that are widely acknowledged and we won't repeat them here. The main idea of this post is to show how we reorganized our specs to avoid circular dependency issues during build.

One of our library repo consists of many Angular components as well as corresponding specs. The structure is like this:

/<workspace>/
|---apps/
|---libs/
	|--- autocomplete                
		|--- autocomplete.component.ts                
		|--- autocomplete.component.spec.ts          
	|--- button               
		|--- button.component.ts               
		|--- button.component.spec.ts          
	|--- option              
		|--- option.component.ts              
		|--- option.component.spec.ts

For example, we have an option component, which is referenced by autocomplete component. There are certain functionality in option component only happens when its parent component is autocomplete. To fully test option component, we would need to spin up an autocomplete component. Because all the components and specs are living in the library, building library would get into circular dependencies issue. There are certainly other ways to get around by building a mock component. So in our example above, we'd build a mock autocomplete component, then option component spec wouldn't depend on autocomplete component, thus no circular dependencies issue. However, depending on the complexity of a component, building a mock component could be cumbersome. We opted not to do it that way due to the complexity.

We end up building our specs in a separate folder

/<workspace>/          
|---apps/          
|---libs/               
	|--- autocomplete                     
		|--- autocomplete.component.ts               
	|--- button                   
		|--- button.component.ts               
	|--- option                    
		|--- option.component.ts          
|---specs/                
	|--- autocomplete.component.spec.ts                
	|--- button.component.spec.ts                
	|--- option.component.spec.ts

Meanwhile we still keep the same spec reference in angular.json as:

        "test": {
          "builder": "@nrwl/jest:jest",
          "options": {
            "jestConfig": "libs/button/jest.config.js",
            "tsConfig": "libs/button/tsconfig.spec.json",
            "passWithNoTests": true,
            "setupFile": "libs/button/src/test-setup.ts"
        }

Inside individual component's jest.config.js, we add the source location of the spec files:

       roots: [
          './src/lib',
          '../../../specs/ui-button',
       ],

This roots attribute tells jest where the spec files are located. In this case, it'd look for all the specs inside component directory as well as in the root level specs directory.

By now, we have configured all our spec files to be included in the test. But we still need to update where the test coverage should be collected from. In root level jest.config.js, we add these lines:

      collectCoverageFrom: [
         `**/*.{ts,js}`,
         `libs/**/*.{ts,js}`,
         `!**/*.module.ts`,
      ],

Now when the library is built, there won't be any circular dependency issues any more, and code coverage report is generated properly.