Releases: getsaf/shallow-render
v6.2.1
v6.2.0
#64 Add support for ViewChild
and ContentChild
class selectors
#66 Fix typing miss in Shallow.alwaysReplaceModule
#59 There are good uses for providing a way to add module imports to an existing module.
This can now be done simply by using .import(ModuleToImport)
:
beforeEach(() => {
shallow = new Shallow(MyComponent, ComponentModule).import(BrowserAnimationsModule);
});
Or, if you want to import it for all specs globally:
Shallow.alwaysImport(BrowserAnimationsModule);
Thanks @kylecannon for helping iron out the use case for this.
v6.1.3
v6.1.2
Allow mocking component instance properties
When a component is written using the template-hash pattern, we sometimes need to mock methods on these components when we use them. For example:
in MyComponent
we may render something like this:
<list-container #container>
<list-item (click)="container.collapse()">Collapse the parent!</list-item>
</list-container>
Shallow will provide us a mock of list-container
and list-item
, but if we want to write a test that ensures we hooked up the click
handler correctly, we'll have to call the collapse
method on the list-container
component so we'll need to mock that method out. Before this change, Shallow would not apply mock properties to component instances but now we can.
const {find} = await shallow
.mock(ListContainerComponent, {collapse: () => undefnied})
.render();
find('list-item').triggerEventHandler('click', {});
expect(findComponent(ListContainerComponent).collapse).toHaveBeenCalled();
Other minor features/fixes
- feat: Shallow now includes schemas from Mocked angular modules
- fix: Fixed an issue where some helpful error messages were showing up as
[object Object]
instead of the actual message
v6.1.2-0
v6.1.1
Support for testing structural directives
Fixes #41
The general idea here is that when testing structural directives, you may simply test for the existence (or not) of the Rendering#element
property.
Positive test case (just what you'd expect):
it('shows content when value is "foo"', async () => {
const {element} = await shallow.render(`<div *showIfFoo="'foo'">Show Me</div>`);
expect(element.nativeElement.textContent).toBe('Show Me');
/* -- or simply -- */
expect(element).toBeDefined();
});
Negative test case:
it('does not show content when value is not "foo"', async () => {
const {element} = await shallow.render(`<div *showIfFoo="'bar'">Show Me</div>`);
expect(element).not.toBeDefined();
});
v6.1.0
Wait for fixture.whenStable()
on render by default
POTENTIAL TEST BREAKAGE NOTICE
It's possible that this breaks a certain category of tests. If this happens for you, the simple fix is to disable
whenStable
on the render for those tests. eg:shallow.render('<foo></foo>', {whenStable: false});
.
It is common for components to implement OnInit
and perform some sort of asynchronous initialization when the component is loaded. When using TestBed
to test your component, you would generally use some sort of fakeAsync
/tick
combo (ugh..), or use fixture.whenStable()
to wait for the async queue to be expunged.
When testing with Shallow, you generally have all your async service calls mocked out before you render your component so we could/should handle this async queue expunging automatically.
Here's a simple example of an async init:
@Component({
selector: 'my-component',
template: '<div *ngIf="!loading">{{data.name}}</div>'
})
class MyComponent extends OnInit {
data: MyServiceResponse;
get loading() { return this.data === undefined; }
constructor(private _myService: MyService) {}
async ngOnInit() {
this.data = await this._myService.loadStuff();
}
}
Our test setup:
beforeEach(() => {
shallow = new Shallow(MyComponent, MyModule)
.mock(MyService, {loadStuff: () => Promise.resolve({name: 'FOO'})});
});
Before this change, you'd have to handle it manually like so:
it('displays the name', async () => {
const {find, fixture} = await shallow.render();
await fixture.whenStable(); // boilerplate..
fixture.detectChanges(); // more boilerplate..
expect(find('div').nativeElement.textContent).toBe('FOO');
});
Or alternatively there's fakeAsync
(I'm not a fan of this if you can't tell already):
it('displays the name', fakeAsync(async () => {
const {find, fixture} = await shallow.render();
tick(); // so weird!
fixture.detectChanges();
expect(find('div').nativeElement.textContent).toBe('FOO');
}));
We can do better:
After this change, it's a little easier:
it('displays the name', async () => {
const {find} = await shallow.render();
expect(find('div').nativeElement.textContent).toBe('FOO');
});
Because we now automatically wait for fixture.whenStable()
we can eliminate the extra steps. Small win!
What if I want to test the component state while the async requests are pending?
You can disable the whenStable
check with a parameter to the shallow.render()
options:
it('does not display anything while loading', async () => {
const {find} = await shallow.render({whenStable: false});
expect(find('div')).toHaveFound(0);
});
Fix replaceModule
bug that caused replacement module providers to be mocked
There was an issue that caused Shallow to mock providers on ModuleWithProviders
replacement modules. This was identified in #43 (thanks @kevinbeal).
For example, using:
shallow.replaceModule(
FooModule,
{ ngModule: FooTestModule, providers: [TestFooService] }
);
Would result in the providers array being mocked but this is never necessary when we replace modules. As a work-around, you could use .dontMock
on the providers but it's a weird looking and obvious hack around this bug.
After this fix, nothing in a replacement module is ever mocked.
v6.1.0-0
v6.0.6
Static Function Mocks!!
class MyClass {
static reverse(thing: string) {
return thing.split('').reverse();
}
}
can be mocked with:
shallow.mockStatic(MyClass, {reverse: () => 'mock reverse'});
Regular objects can be mocked in this manner too:
const FOO = {
bar: () => 'bar'
};
Can be mocked with:
shallow.mockStatic(FOO, {bar: () => 'mocked bar'});
Due to Jasmine spy limitations, only methods are supported. If you try to mock a non-method property, an error is thrown. When we begin supporting other test frameworks this limitation may go away (or only exist when using Jasmine).
class MyClass {
static foo = 'FOO'
}
If you try to mock non-method property MyClass.foo
will throw an error:
shallow.mockStatic(MyClass, {foo: 'MOCK FOO'}); // throws: InvalidStaticPropertyMockError
Support ModuleWithProviders in Shallow constructor
Sometimes, you may want to test your component's module uses the forRoot
pattern. Previously, you would have to manually .provide()
any service dependencies when your module used the forRoot
pattern but with this fix, you can use the forRoot
output directly in the Shallow
constructor:
Module
@NgModule({
declarations: [MyComponent]
})
class MyModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MyModule,
providers: [MyService],
}
}
Before
shallow = new Shallow(MyComponent, MyModule)
.provide(MyService);
After
shallow = new Shallow(MyComponent, MyModule.forRoot());
fix: Make dontMock work provider arrays
There was a bug that would prevent dontMock
and neverMock
from skipping mocks for providers in a forRoot
array unless you used the exact same array reference in your dontMock
call.
For example:
@NgModule()
class MyModule {
static forRoot(): NgModuleWithProviders {
return {
ngModule: MyModule,
providers: [FooService, BarService]
}
}
Mocking the forRoot
providers like this, would not work because each call to forRoot
yields a new array reference.
shallow.dontMock(MyModule.forRoot().providers);
This fixes that bug so it works now.
v6.0.5
Two new features here:
- Allows rendering without specifying the HTML template while also binding directly to the component properties. Thanks for the idea @ike18t!
- Automatically spy on
@Output
EventEmitters
Example:
@Component({
selector: 'my-component',
template: '<span (click)="clickName.emit(name)">{{name}}</span>'
})
class MyComponent {
@Input() name: string;
@Output() clickName = new EventEmitter<string>();
}
it('can bind directly to inputs', async () => {
const {find} = await shallow.render({bind: {name: 'FOO'}});
expect(find('label').nativeElement.textContent).toBe('FOO');
});
it('automatically spys on @Outputs', async () => {
const {find, instance} = await shallow.render({bind: {name: 'BAR'}});
find('label').click();
expect(instance.clickName.emit).toHaveBeenCalledWith('BAR');
});