DEV Community

TDD in Angular - Dependency Injection and Mocking

Bearded JavaScripter on May 28, 2020

In our last article here, we went through the basic structure of an Angular Unit Test and went on to test services. In this article, I want to show...
Collapse
 
stealthmusic profile image
Jan Wedel • Edited

I am struggling with TDD in Angular ever since I have started and I was really hoping to find some answers here.

In the backend (Java), TDD just comes naturally.

But in the UI, there is more than services and component logic. There is layouting and a mixture of those. So, I often ask myself where do I start to write tests?

I am just developing a complex master/detail component and I was fiddling with choosing the right display material components, wiring data bindings etc until I wrote the first test.

This actually made me feel really uncomfortable, actually kind of dirty.

I would love to start with Cypress tests, then probably layouting, then Unit tests and then implement logic but just doesn’t work. I tried.

I am wondering how you Or others do it?

One last bit: Using TestBed integration tests slows down the test speed significantly (from 20md each test to 3-4seconds) for a complex component. That is inacceptable. So we refactored all jasmine tests to be pure unit tests of components and to all DOM testing in Cypress.

Collapse
 
qarunqb profile image
Bearded JavaScripter

Bear in mind, I have a lot more articles coming up that definitely touch and explain more on the topics in this thread. It's just a matter of getting them out but I'm really glad to see that all these points come up so I know what to focus on in my articles and what to prioritize.

Really grateful for all your comments :D ❤️

Collapse
 
stealthmusic profile image
Jan Wedel

Looking forward to that!

Collapse
 
qarunqb profile image
Bearded JavaScripter

As per the Angular Style Guide, we try our best to keep the application logic in services and anything to do with UI in components. So you can test the majority of your application logic through testing services and, instead of accessing the DOM from jasmine/jest, you can leave that to cypress.

Collapse
 
stealthmusic profile image
Jan Wedel

Yeah, mostly yes. But you’d still have mappings from backend to component models, validation logic, event emitters and stuff you want to test.

But my question was more like: Can you build a whole angular application test-driven? Does it even make sense?

It just feels bad not to TDD everything to me but it doesn’t work when ever I try.

Thread Thread
 
qarunqb profile image
Bearded JavaScripter

When you say mappings from backend to component models, validation logic, event emitters, what do you mean?

Do you mean testing http requests, validation of data submitted to backend routes and testing Observables, events received from the backend?

Angular allows you to test those things using an HttpInterceptor, so you can create a mock backend. In terms of unit testing, you'd want to isolate your front end from your back end. You can connect them when it's time for an e2e tests, most of my articles cover Unit Testing for now, e2e is definitely something I have to get into more.

If your back end is already built, you can streamline your mock backend to mimic how data is supposed to be returned, throw in a few errors just to see how your front end handles it.

I could be misunderstanding what you're referring to though.

Thread Thread
 
stealthmusic profile image
Jan Wedel

I mean things like:

  • mapping a backend model (which is returned by an angular service) to a component model that contains all and only the information in appropriate data format that it can be used in templates.
  • Form validation logic In complex non-html-formS

- Cross-component event that need to be handled correctly

Thread Thread
 
qarunqb profile image
Bearded JavaScripter

I think I'm getting confused here. When you say backend, do you mean your services or your actual back end server? You can share interfaces and type formats throughout your entire application. If your server is also in TypeScript, you can share your types and interfaces with both front end and back end. If not then you'll need to manually create your interface types within your application.

Angular Http calls from a service can return the data model and format required by the calling code

Very simplified example below

interface User {
   name: string;
   id: number;
   age: number;
}

this.http.get<User[]>('/api/users')

In this way, the data returned by your back end should be of that User format. TypeScript doesn't strictly check that, but the type annotations are there for your IDE. If there are fields that absolutely must be there, then you might wanna write functions to make sure that those fields are there, which is also a good form of unit testing as well

With respect to the second point, I'm not sure what you mean, but ReactiveForms in Angular are made with TDD first in mind since they are created in the TypeScript before the HTML is initialized. Can you tell me more about non-html-forms?

With respect to testing events between components directly related, check out this article here.

For components that are connected via a service, your unit tests should only test the events (RxJS or otherwise) in that service only. Bringing in other components to test the events from a service isn't unit testing anymore, it's more integration testing.

I'm really sorry if I'm not answering any questions you might have :/

Thread Thread
 
qarunqb profile image
Bearded JavaScripter • Edited

Also, with respect to one of your previous questions, yes it is possible to build an entire Angular app using TDD, it helps to promote a nice structure in your application.

However, aiming for 100% test coverage is difficult for pretty much any codebase, so I would personally advise testing Business Logic and core requirements first and then the rest of the tests can come after.

Keep in mind that some business logic could be UI related as well, so that's where Cypress e2e tests would come in, not necessary unit tests

Thread Thread
 
stealthmusic profile image
Jan Wedel

When you say backend, do you mean your services or your actual back end server?

By backend, I mean the backend server. By backend model, I mean a typescript class/interface that maps to the data model from the backend.

By component model, I mean a data structure that holds all the state/fields of the component which is data binded from the parent component and represents the UI better.

So in your example, User would be a backend model. We don't have a backend-for-frontend architecture but multiple backends. For example, one businiess entitiy that references a user by ID. Then we need to load the appropriate user as well to display it name in the ui.

The frontend model e.g. contains selected chip options for a autocompletable chip list rather that an array of User entites etc.

With respect to the second point, I'm not sure what you mean, but ReactiveForms in Angular are made with TDD first in mind since they are created in the TypeScript before the HTML is initialized.

I know about reactive forms but haven't tried them. Maybe I should have a look on them. We use template driven forms but we don't use the HTML submit. Instead, we call the backend with a POST/PUT when the form is "saved". Then, we need some cross-field validation logic that also can/should be unit tested to cover all the edge cases.

I hope that clears my comment up a bit.

However, aiming for 100% test coverage is difficult for pretty much any codebase, so I would personally advise testing Business Logic and core requirements first and then the rest of the tests can come after.

Noone should pointlessly aim for 100% coverage. Why I really meant is writing tests before writing any line of component code, including template and code.

When I tried that, I really didn't know how to start. I first needed to play around with material components, learn how they work, what there interfaces are... So I couldn't really write a Cypress test before because I din't know what HTML element I should select for expectations...

Thread Thread
 
qarunqb profile image
Bearded JavaScripter

Thanks a lot for the clarifications.

With respect to the forms, it's really up to the developers on how they wanna handle the validation. Once you decide that, the unit tests will follow where your validation is.

So for example:

<form action = '/create-user' method = 'POST'>
...
</form>

There really isn't much unit testing that could be done on the front end in terms of validation. An Angular HttpInterceptor can technically still catch the request before it goes off to the server, so it can grab the body and perform validation checks there, but you can also do the validation checks on the server and return the appropriate response based on the form value.

With respect to a ReactiveForms declaration inside a component as follows:

form: FormGroup;

constructor(private fb: FormBuilder) {
  this.form = this.fb.group({
    name: this.fb.control('', Validators.required),
    email: this.fb.control('', [Validators.required, Validators.email]) 
  });
}

This form can be unit tested and validated entirely on the client side to ease Validation on the server side. You can find many of the Validators here. And you can even stitch together custom validators to consider weird cases as well. Angular also comes with Sanitization for user input as well, though I have to do some more research on it.

Thread Thread
 
layzee profile image
Lars Gyrup Brink Nielsen • Edited

@stealthmusic

When I tried that, I really didn't know how to start. I first needed to play around with material components, learn how they work, what there interfaces are... So I couldn't really write a Cypress test before because I din't know what HTML element I should select for expectations...

This is one of the issues that component harnesses address. You'd have to implement a HarnessEnvironment for Cypress though.

mapping a backend model (which is returned by an angular service) to a component model that contains all and only the information in appropriate data format that it can be used in templates.

There are many ways to approach this. I prefer creating input properties with data types that are the most convenient for presentational components and then doing mapping either in container components or services used by container components.

We can then easily test the mapping in isolated unit tests and there's less business logic in our presentational components.

Collapse
 
layzee profile image
Lars Gyrup Brink Nielsen • Edited

The first call to ComponentFixture#detectChanges runs ngOnChanges and ngOnInit.

Collapse
 
qarunqb profile image
Bearded JavaScripter

Thank you for the correction! Will edit this now

Collapse
 
bodote profile image
bodote

I think MockInventoryService is not necessary here. You can make a spy without it.
Your tests would do just fine, if you use the real service, but using the spyOn()'s .and.returnValue() method, or any other of spyOn()'s methods, to make the spy do what you need for your test.
That would save you a lot of lines of code.

Collapse
 
bravemaster619 profile image
bravemaster619

Angular TDD is really annoying when you have to inject every dependency to TestBed for each component.

Collapse
 
qarunqb profile image
Bearded JavaScripter

Yep it can get annoying at times. At that point, there's usually a compromise to be made between the number of dependencies for a component and what the conponent can do in terms of features.

There are articles that say to keep the dependencies per component less that 3, but sometimes it's inevitable