Angular Testing: Revisited

Quantarius Ray
5 min readOct 20, 2022

--

Angular testing is in a weird spot! It’s not a bad thing, but it’s true.

Well… testing, itself, isn’t in a weird spot, but the terminology certainly is. It’s almost impossible to know what type of testing people are talking about without asking clarifying questions. If you’ve done any testing at all, especially in enterprise development, you’ve probably heard of “automated testing,” “unit testing,” “integration testing,” “component testing,” and “end-to-end testing.” Of course, there are more, but these are the types you will constantly hear brought up in conversation and if you’ve ever done a quick Google search. But what are each of these? Which of them are crucial to the long-term success of your project? And which tools make the most sense for each of these?

Automated Testing

This is an easy one to answer:

Automated Testing is any testing you don’t perform manually.

I’ve heard developers and non-developers refer to end-to-end tests as “automated testing” but exclude “unit testing;” however, if we’re being true to the definition, any type of test that runs or validates pieces of our code or workflows is “automated.”

Unit Testing

Unit testing is the next easiest to explain. It’s the first type of testing a developer learns (in a sense), and in Angular, you get free unit tests out of the box as part of your generated `.spec` files (eh…).

Given this piece of code:

getSum(a: number, b: number): number {
return a + b;
}

You would write unit tests similar to this:

it('should return a sum of the provided numbers', () => {
expect(testClass.getSum(1, 2).toBe(3));
}

This class and the test could be better, but this would at least get you started. Generally, you would create unit tests to test business logic and as a quick means to ensure what you’re injecting in your components is functionally correct. Unit tests are great for developers and other developers using their functionality in their code. If all code is properly unit tested, you have peace of mind knowing that you can safely use any piece of functionality in your codebase and in your new code.

Modification of the original testing pyramid

Usually, you’ll use Jasmine or Jest to write your unit tests. If you’re looking at a traditional testing pyramid, unit tests are always at the bottom. It’s not uncommon to have two or three unit tests to cover one piece of functionality. This means they provide the least value to the end-user and the most value to developers, and because they are small, they run faster than other tests.

Component Testing

Component Testing is undoubtedly the most important type of testing, in my opinion. In Angular 14+, standalone components are all over the place, and all of your business logic should live in services, so most unit tests are testing services and pipes. Components are large chunks of dynamic HTML with properties passed from your component.ts file to your component.html file; this means you would mostly need to test that these values and properties are reflected correctly in the DOM.

Angular allows you to component test out of the box with TestBed.

const fixture = TestBed.createComponent(HeaderComponent);
const testComponent = fixture.componentInstance; // gets the component reference of the component being tested.
it('should have a title', () => {
const title = fixture.debugElement.query(By.css("h1")).nativeElement; // gets the h1 element within the component
expect(title.innerText).toBe("Test"); // aserts that the expected value to be the value provided}

Back in 2017, Storybook began supporting Angular applications and allowed us to harness our components in complete isolation and manually test the component’s @Inputs and @Outputs, responsiveness, and accessibility features. It was an excellent way for design systems and component teams to share their components with other developers and allow them to play with the API without adding it to their codebase. Fast-forward to 2022; you can now automate these tests with Cypress.

That same test above can now be done with Cypress’ format:

cy.mount(HeaderComponent);it('should have a title', () => {
cy.get('h1') // get the h1 element within the component
.should('have.text', 'Test') //asserts that the text should be Test within the header
});

These tests also run directly in the browser with a very minimal setup, and because they are small pieces of code, you can run just as many of these tests as you can run unit tests.

Looking at a traditional testing pyramid, component testing would fall snuggly between unit tests and integration tests. Since end-users interact directly with the components in your application, it makes sense that heavily testing your components makes the most sense. If you’re neglecting any testing, it should not be component testing.

Integration Testing

I think the Cypress team made an error in the earlier framework versions. The cypress directory was initially created with an “integrations” folder, which, for someone as impressionable as me, took to mean the types of tests I made were integration tests. In a sense, they were. I’ll do my best to explain:

An integration test, by definition, is a type of test that tests how multiple units or components work together.

It’s possible to have all your functionality and components tested to the brim, but once another developer uses your component with someone else’s component, how do the two behave together?

Generally, integration tests are written by accident. Non-isolated tests, testing workflows, and improper use of spies or spyObjects will create an integration test. For example, assume I click a button, and the action populates a form with data; an integration test would test when I click that button. Does the data properly get inserted? That leads me directly to end-to-end testing.

End-to-End Testing

In the front-end world, integration tests and end-to-end tests are interchangeable. An end-to-end test is supposed to test that one action on the front end properly does what it’s supposed to on the back end. So, for example, if I complete a form and save it, is that data properly saved to the database?

Again, Cypress is great at this as well. It can monitor network traffic directly, or if you don’t want to send requests to a real back-end, you can create fixtures that simulate complete end-to-end workflows. The only real difference between integration and end-to-end tests is what you assert. An integration test doesn’t care that the data sent to the back end properly save to a database. However, it does care about the steps that lead up to that point.

So, with these types of tests defined, you can now correct anyone who misspeaks on test type. You should also ensure that you write practical tests for the right class types. Component test your components! You should be using Cypress for this, there is absolutely no reason not to be in 2022. Unit test your business logic, whether that be with Jest or Jasmine, both work fine. And End-to-End test your workflows that matter. Again, Cypress is excellent for just writing integration tests OR going all the way and writing end-to-end tests. But most importantly, remember that all these tests are automated :)!

--

--

Quantarius Ray

I am a staff engineer at Bill and a Cypress Ambassador. I enjoy Angular, Testing, and Accessibility!