Lesson 19

Refactoring with Confidence

Make changes to your application without the fear of breaking it!

PRO

Lesson Outline

Refactoring with Confidence

Our application isn't the most attractive looking piece of software in the world - it's completely bare bones with no styling at all - but it does its job. Given that we have strictly adhered to the Test Driven Development methodology, we can be reasonably confident that it does the tasks that it is supposed to do: the requirements that we built into it via the test we wrote.

Having all of these tests is a great investment for the future, as they will always be sitting there protecting against regressions when any changes are made in the future. This means that we can confidently refactor the application, or add new features, without having to worry too much about breaking existing functionality.

Perhaps we have shelved this application for a while, or maybe we are coming on as a new developer to this project, and we see the following code in src/app/home/home.page.ts:

	ngOnInit(){

		this.modules = [
			{id: 1, title: 'Module One', description: 'Test'},
			{id: 2, title: 'Module Two', description: 'Test'},
			{id: 3, title: 'Module Three', description: 'Test'},
			{id: 4, title: 'Module Four', description: 'Test'},
			{id: 5, title: 'Module Five', description: 'Test'}
		];

	}

The modules array is being manually defined in the ngOnInit function, and our implementation of the actual modules service isn't even complete at this point (it is still using dummy data). Let's say we decide it's time to change this. We know that the application is well tested, so we don't need to worry too much about breaking something inadvertently with this refactor - if we do unintentionally break something that was previously working then the tests are likely going to notify us.

Let's give that a go. We will refactor this code to load in its data from the ModuleService instead. Usually we start with an E2E test whenever we are introducing some new functionality, but in this case we already have our tests in place - we are just changing the implementation. Sometimes this will mean having to update the unit tests, but since we are using more "behavioural" style tests, e.g:

the modules class member should contain 5 modules after ngOnInit has been triggered

the way in which we set up the modules array doesn't really matter, just as long as after ngOnInit is triggered it is there. Given that these tests are in place and we don't need to modify them, we are going to jump straight into pulling data from our ModuleService.

We are still just going to hard code the data into the service, but we could easily change it to instead load from some external source as well. Let's add a simple test to our spec file to cover this new functionality.

Modify src/services/module.service.spec.ts to reflect the following:

import { TestBed } from '@angular/core/testing';

import { ModulesService } from './modules.service';

describe('ModulesService', () => {
  let service: ModulesService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(ModulesService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('getModules should return a non-empty array', () => {
    const result = service.getModules();
    expect(result.length).toBeGreaterThan(0);
  });
});

We have just added a single test that checks to see if getModules returns a non empty array. This is a more granular unit test, it's not behavioural like the previous test we discussed. In this case, if we were to change this in the future to load the data in from a server, for example, then the test would need to be updated because it would no longer return an array (it would likely return an observable instead).

Let's add our dummy implementation:

  getModules() {
    return [];
  }

If we run the unit tests now with npm test we should get the following error:

 FAIL  src/app/services/modules.service.spec.ts
  ● ModulesService - getModules should return a non-empty array

    expect(received).toBeGreaterThan(expected)

    Expected: > 0
    Received:   0
PRO

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).