Tuesday, March 27, 2018

Angular 5 HttpClient Assumes JSON

In Angular 4 (I think) HttpClient was introduced to make remote calls easier. I didn't have a problem using it until I needed to return raw HTML from a remote API (which I'll write about in another post because it was very interesting) and found out that HttpClient assumes the result of the remote call is in JSON format. I totally understand why they went that route, because JSON is the most common (and IMO the best) format for data returned from a remote API. I really needed raw HTML (content type text/html) so it was a problem for me.

Luckily, right there in the docs is the way you're supposed to handle this scenario. All we have to do is specify a second parameter that declares the expected response type. It's not ideal, but given that (at least in my experience) somewhere around 99% or remote calls return JSON I'm OK with this approach... now that I know the solution for the 1%.

Tuesday, March 20, 2018

The Unhelpful [object ErrorEvent] Message When Testing Angular

A few times in the past two weeks my tests have started failing with a message that was rather less than helpful. Instead of indicating what might actually be the problem somewhere I simply see
"[object ErrorEvent]", which is right on par with the wonderful "object reference not set to an instance of an object" exception in .Net.

The first time I encountered this issue, I found a blog that lead me to the right answer. You can read the other guy's post here. This time around, the error message was the same, but the solution was different. Since this error obviously has more than one underlying problem, I was very glad to stumble upon an SO answer (here) that said to restart the tests with the --sourcemaps=false flag.

As soon as my test ran with that flag set that way I got a helpful error message that lead me to the root of the problem: "view.root.sanitizer.sanitize is not a function". If you're having the same problem, try setting the sourcemaps flag to false when you run your tests and see if the better exception message allows you to hunt down what's happening.

Oh, since I know this will come up for me again, the actual problem I was having was caused by the DomSanitizer service. I'm injecting a mock service in the constructor for my tests, but my mock service only had the function I was actually calling in it. Since DomSanitizer extends Sanitizer there should have also been a sanitize function on my mock. The solution ended up being adding a sanitize function, but because of the terrible, unhelpful error message it took me several hours to figure that out.

Monday, March 19, 2018

Testing ViewChildren in Angular

I have a component that uses a multi-select-dropdown (MSD) component. There can be many MSDs on a single page. When one of the MSDs is opened, I want to close all of the others. There's a function called close on each MSD, and each MSD also has an id property. Finally, the MSDs are created dynamically based on some remote data (so they don't have template reference names).

So how do I close every MSD on the page except the one I'm currently trying to open? It turns out I can get references to all of the MSDs on the page by using ViewChildren. I have a property on my component (my page component) that looks like this
@ViewChildren(MultiSelectDropdownComponent) dropdowns: QueryList<MultiSelectDropdownComponent>;
Now that I know how to get access to them, I have a new problem: how to I populate the dropdowns property from my unit tests?

It turns out that's really easy, too. I just need to let the TestBed do its thing. Since my MSDs are created using an ngFor on another property on my page component (called items), all I have to do is populate my items property, then rebuild the test component. That will build the MSDs for me. That looks like this:
const spies = [];
component.items = [
 {id: 1, options: [...]},
 {id: 2, options: [...]},
 {id: 3, options: [...]}
];
fixture.detectChanges();
component.dropdowns.forEach(d => {
  spies.push({
    id: d.id,
    spy: spyOn(d, 'close')
  });
});
When we invoke fixture.detectChanges our markup is rebuilt, which generates our three MSDs on the test form. Once that happens, the MSDs are available in the dropdowns property so I can iterate through the dropdowns property and spy on the close function of each separate MSD.

Now that I'm spying on the close function of each MSD, I want to invoke my function under test (called toggleOthers) and make sure that all the other MSDs are closed, but not the one that invoked toggleOthers in the first place (because I'm actually trying to open that one).
component.toggleOthers(2);

spies.forEach(s => {
  if (s.id === 2) {
    expect(s.spy).not.toHaveBeenCalled();
  } else {
    expect(s.spy).toHaveBeenCalledTimes(1);
  }
}
Since we invoked the toggleOthers function with the id we're opening, we can check to make sure the MSD associated with that id isn't closed. Then we can check that all other MSDs were closed.

The caveat to this approach is that if the MSD was complex we wouldn't really want to bring the real MSD into the tests just so we could do this. I'm sure that'll come up for me someday so if it does I'll try to remember to write a post on that and update this one with a link to it. If that's what you're looking for, sorry. Hopefully this helps you get there.

Friday, March 16, 2018

Testing Angular Components That Use Pipes

Pipes are a common feature in Angular that are often used to do things like format data (read more about Pipes in the Angular documentation). Unfortunately, when we use Pipes in our Components they can cause some issues with testing. In order to test a Component that uses a Pipe you need to either bring the Pipe in to the TestBed or (my preference) create a Fake Pipe and instruct your test to use that instead.

I prefer this method because it means I can control the functionality of the Pipe, which usually means replacing whatever the Pipe actually does with nothing. I can also more easily manipulate my tests to provide expected outputs, which is always helpful.

Let's say you have a Component whose template uses a Pipe to translate something. The real TranslatePipe accepts a parameter called "query", looks up the query value in a file, and returns the value found in the file. It's basically a key/value lookup. When we're unit testing we want to make sure we're testing the smallest possible unit, which means we should have already tested the real TranslatePipe. Since we can trust that the real TranslatePipe works as expected, we don't want to actually use the TranslatePipe in our Component tests. Instead we'll create a fake TranslatePipe.

When we use the Angular CLI to generate our components we also get a spec file for that component, which has some boilerplate code in it that probably looks something like this:
/* Import statements up here */

describe('CoolComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [CoolComponent]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(CoolComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
Let's say the very first thing we're going to do on our CoolComponent is use our TranslatePipe to display a title on the page. We know the title of the page will need to be translated and we know we have the TranslatePipe. What we'll want to test is whether the TranslatePipe is invoked during rendering.
it('should display translated title', () => {});
Before we can actually write our test we'll need to create our FakeTranslatePipe and instruct the TestBed to use it instead of the real TranslatePipe. In the same file*, before the describe block, but after the imports, add the following code:
@Pipe({name: 'translate'})
export class FakeTranslatePipe implements PipeTransform {
  transform(query: string): any {
    return 'Translated!';
  }
}
Now that we've created the FakeTranslatePipe, we'll instruct the TestBed to use it by supplying it to the declarations array. The updated beforeEach looks like this:
beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [
      CoolComponent,
      FakeTranslatePipe
    ]
  })
  .compileComponents();
}));
When our component markup uses the translate pipe, our FakeTranslatePipe will be used instead of the real TranslatePipe. That's all we have to do to avoid an error, but if we want to test whether the Pipe was actually invoked during rendering we'll need to create a spy on it. Create a variable for the spy inside the describe block, but outside the first beforeEach.
let pipeSpy: jasmine.Spy;
After we compile the components, but while we're still inside the first beforeEach, we'll spy on FakeTranslatePipe's translate function. Because we're spying on a function that exists on a class instead of an instance we'll have to spy on the prototype of FakeTranslatePipe. It's easy enough to do once you know to do it. The spy creation looks like this:
pipeSpy = spyOn(FakeTranslatePipe.prototype, 'transform').and.callThrough();
Now we can treat pipeSpy like other spy (that is, we can check whether it was called, how many times, with what parameters, etc.) in our tests. Here's what our test might look like now:
it('should display translated title', () => {
  expect(pipeSpy).toHaveBeenCalledTimes(1);
  expect(pipeSpy).toHaveBeenCalledWith('page-title');
});
We're really close to being finished, but all we know is that our FakeTranslatePipe was invoked. We don't know whether we put the result in an h1 tag. We can add that to our test so the end result looks like this:
it('should display translated title', () => {
  const debugElements = fixture.debugElement.queryAll(By.css('h1'));

  expect(debugElements.length).toBe(1);
  expect(debugElements[0].nativeElement.textContent).toBe('Translated!');
  expect(pipeSpy).toHaveBeenCalledTimes(1);
  expect(pipeSpy).toHaveBeenCalledWith('page-title');
});
The last thing I'll leave you with is the final version of the whole spec file.
import { Pipe, PipeTransform } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { CoolComponent } from './cool-component';

@Pipe({name: 'translate'})
export class FakeTranslatePipe implements PipeTransform {
  transform(query: string): any {
    return 'Translated!';
  }
}

describe('CoolComponent', () => {
  let pipeSpy: jasmine.Spy;
  let component: CoolComponent;
  let fixture: ComponentFixture<CoolComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        CoolComponent,
        FakeTranslatePipe
      ]
    })
    .compileComponents();
    
    pipeSpy = spyOn(FakeTranslatePipe.prototype, 'transform').and.callThrough();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(CoolComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should display translated title', () => {
    const debugElements = fixture.debugElement.queryAll(By.css('h1'));
    
    expect(debugElements.length).toBe(1);
    expect(debugElements[0].nativeElement.textContent).toBe('Translated!');
    expect(pipeSpy).toHaveBeenCalledTimes(1);
    expect(pipeSpy).toHaveBeenCalledWith('page-title');
  });
});



*If we know we'll be using the same FakeTranslatePipe in lots of different spec files throughout the project, we could create it in a separate file and import it, but for the purposes of this post we're going to create it in the same file.

Bootstrap Breakpoints

It seems like I'm constantly looking up Bootstrap's breakpoints so I figured I should just put them here for (my own) easy future reference.

XS (Extra Small) SM (Small) MD (Medium) LG (Large) XL (Extra Large) XXL (Extra Extra Large)
V5 <576px 576px ‐ 767px 768px ‐ 991px 992px ‐ 1199px 1199px - 1399px >1399px
V4 <576px 576px ‐ 767px 768px ‐ 991px 992px ‐ 1199px >1199px N/A
V3.3.7 <768px 768px ‐ 991px 992px ‐ 1199px >1199px N/A N/A
CDK <600px 600px - 1024px 1024px - 1439px 1440px - 1919px >1919px N/A

Now that I've put this table together I realize that part of my confusion is that Bootstrap 4 added the XL breakpoint, which caused the scales to shift. Good to know.
Edit: I've added the Angular CDK breakpoints
Edit: I've added the Bootstrap V5 breakpoints

Tuesday, March 13, 2018

Visual Studio Code

I've been using VS Code for a while and I absolutely love it for all of my front-end editing needs. However, there are some shortcuts that I got used to in Visual Studio over the years that are different in VS Code. Fortunately, there's a way to easily rebind shortcuts in VS Code. Listed here are a few of my favorite changes to make to VS Code to get it setup the way I like it.

To change key bindings go to File > Preferences > Keyboard Shortcuts. Once in there you can search for the action and then double-click on it to change the binding. Below is a table with the default VS Code bindings, their corresponding Visual Studio bindings (if applicable), and the action taken. I change all of these before I do anything else in VS Code.

Action VS Code (Default) Visual Studio (Default)
Fold All Ctrl+k, 0 Ctrl+m, Ctrl+o
File: Save All Ctrl+K, S Ctrl+Shift+s 
File: Save As... Ctrl+Shift+s

I found that in recent versions you have to remove the binding for File: Save As... for the new File: Save All binding to work. Just right-click on the existing binding and choose Remove Keybinding in the context menu.
I also like to turn off the little preview pane that is displayed by default on the far right of the main window. To do that, go to View > Appearance > Minimap.

I'll update the table above as I find more shortcuts that don't match (I'm sure there are plenty). As usual, this list is more for me than it is anyone else so if it doesn't help you... make your own.