angular-unit-component-driver
v0.3.0
Published
Test Angular components in jasmine with component drivers
Downloads
3
Maintainers
Readme
AngularComponentDriver
Test your Angular components in an easiest way with component drivers ! 🚕
Table of contents
- Set up
- Accessing the fixture properties
- Query DOM elements, Directives and Components
- Accessing the injector
- Mock services with spies
- Accessing the TestBed
- Example
Set up
1 - driver creation
Create a class that extends the ComponentDriver class :
class MyComponentDriver extends ComponentDriver<MyComponent> {}
2 - componentTestingSetup usage
The componentTestingSetup
is used to set up the component driver. Create a function that returns the result of the componentTestingSetup
by passing at least your component class and the driver associated to the component :
function testingSetup() {
return componentTestingSetup({
componentClass: MyComponent,
driver: MyComponentDriver
})
}
Other optional parameters can be defined in the conf passed to this method :
{
componentClass: any
driver: any
servicesToStub?: any[]
declarations?: any[]
providers?: any[]
overrideProviders?: any[]
imports?: any[]
}
servicesToSub
(optional) refers to the services that need a mock by creating a Spy. It uses the jasmine-auto-spies package under the hood.overrideProviders
(optional) refers to the component provided services that need a mock by creating a Spy.declarations
,providers
,imports
are optionals and are used in the same way that if you declare the TestBed configurations (because it uses the TestBed behind the scenes)
3 - get your component driver
Get your component driver by calling the componentTestingSetup
method returned by componentTestingSetup
describe('ComponentDriver', () => {
let questionComponentDriver: QuestionComponentDriver
beforeEach(() => {
myComponentDriver = testingSetup().createComponentDriver()
})
})
Accessing the fixture properties
You can easily access to the fixture properties like this :
myComponentDriver.componentInstance
myComponentDriver.detectChanges()
...
Query DOM elements, Directives and Components
You can access to DOM elements, directives and components defined inside the component you are testing.
In your MyComponentDriver class, you have to add the appropriate methods to query what you need. For example :
class MyComponentDriver extends ComponentDriver<MyComponent> {
get questionElement() {
return this.querySelector('.my-element')
}
get buttonElements() {
return this.querySelectorAll<HTMLButtonElement>('button')
}
get firstMyDirective() {
return this.queryDirective(MyDirective)
}
get myChildComponents() {
return this.queryDirectiveAll(MyChildComponent)
}
}
The ComponentDriver class exposes 4 methods to query elements and directives (components) :
querySelector<T = HTMLElement>: (cssSelector: string) => T
querySelectorAll<T = HTMLElement>: (cssSelector: string) => T[]
queryDirective<T>: (directive: Type<T>) => T
queryDirectiveAll<T>: (directive: Type<T>) => T[]
For querySelector
and querySelectorAll
methods, you can specify the type of the queried element (HTMLElement by default).
For queryDirective
and queryDirectiveAll
methods, you don't have to specify the type, it is automatically returned.
Accessing the injector
You can easily access to the injector to get services :
import { Spy } from 'jasmine-auto-spies'
describe('ComponentDriver', () => {
let myServiceSpy: Spy<MyService>
beforeEach(() => {
myServiceSpy = myComponentDriver.injector.get(MyService)
})
})
Mock services with spies
jasmine-auto-spies is used behind the scenes, click on the link to get more informations about how to create mocks in the easiest way you can find 😉
"Creating spies has never been EASIER! 💪👏", Shai Reznik, author of jasmine-auto-spies
- add services to mock in the conf via the servicesToStub array
- access to the spied service via the injector
- mock the returned values of the service methods your component use
function testingSetup() {
return componentTestingSetup({
componentClass: MyComponent,
driver: MyComponentDriver,
// 1 - add services to mock here
servicesToStub: [myService]
})
}
describe('ComponentDriver', () => {
let myComponentDriver: MyComponentDriver
let myServiceSpy: Spy<MyService>
beforeEach(() => {
myComponentDriver = testingSetup().createComponentDriver()
// 2 - get the spied service via the injector
myServiceSpy = myComponentDriver.injector.get(MyService)
// 3 - Mock the returned value, visit the jasmine-auto-spies page to get more informations
questionServiceSpy.getQuestion.and.nextWith(questionData)
})
})
Accessing the TestBed
the componentTestingSetup
method sets up the TestBed under the hood. You still can access it if needed like this :
componentDriver.TestBed
Example
In this example, we use the jasmine-given library to write our test suite with Given
, When
, Then
functions.
Given
: describe the inputWhen
: describe the actionThen
: describe the output
You can use one of my other packages angular-karma-gwt to setup everything you need to use
jasmine-given
and some other cool stuff. A simpleng add angular-karma-gwt
will download the packages and set up everything you need automatically.
Our app
Let's assume that our app is composed by :
- a question component
- a service component
- a component provided service
- an answer directive binded to our element that display the answers
@Component({
template: `
<div class="question">{{ questionData.question }}</div>
<div *ngFor="let answer of questionData.answers" [answerDirective]="answer">{{ answer }}</div>
<button>Previous</button>
<button>Next</button>
`,
providers: [LoggerService]
})
class QuestionComponent {
questionData: QuestionData
constructor(questionService: QuestionService) {
questionService
.getQuestion()
.pipe(first())
.subscribe(question => {
logger.log(question)
this.questionData = question
})
}
}
@Injectable()
class QuestionService {
@AsyncSpyable() // => Decorator from the jasmine-auto-spies package
getQuestion(): Observable<QuestionData> {
return of(null)
}
}
@Injectable()
export class LoggerService {
log(data): void {
console.log(data)
}
}
@Directive({
selector: '[answerDirective]'
})
class AnswerDirective {
@Input('answerDirective') answer: string
}
1 - Create your component driver class
This class can be empty and only extend the ComponentDriver class. But here, we added some methods to query some elements (DOM and directives) of our component to test the UI.
You can query child component in the same way you query directives (components are directives).
class QuestionComponentDriver extends ComponentDriver<QuestionComponent> {
get questionElement() {
return this.querySelector('.question')
}
get buttonElements() {
return this.querySelectorAll<HTMLButtonElement>('button')
}
get firstAnswerDirective() {
return this.queryDirective(AnswerDirective)
}
get answerDirectives() {
return this.queryDirectiveAll(AnswerDirective)
}
}
2 - Write your test suite
function testingSetup() {
return componentTestingSetup({
componentClass: QuestionComponent,
driver: QuestionComponentDriver,
servicesToStub: [QuestionService],
declarations: [AnswerDirective]
})
}
describe('QuestionComponent', () => {
let questionComponentDriver: QuestionComponentDriver
let questionServiceSpy: Spy<QuestionService>
const loggerService: Spy<LoggerService> = createSpyFromClass(LoggerService)
beforeEach(() => {
questionComponentDriver = testingSetup(loggerService).createComponentDriver()
questionServiceSpy = questionComponentDriver.injector.get(QuestionService)
})
// jasmine-given magic here
describe('GIVEN a question is available THEN it is displayed', () => {
let questionData: QuestionData
// Input
Given(() => {
questionData = {
question: 'What do you prefer ?',
answers: ['pizza', 'burger']
}
questionServiceSpy.getQuestion.and.nextWith(questionData)
})
// Action
When(() => {
questionComponentDriver.detectChanges()
})
// Output
Then(() => {
// testing our component data
expect(questionComponentDriver.componentInstance.questionData).toEqual(questionData)
// testing the DOM element that displays the question
expect(questionComponentDriver.questionElement.textContent).toContain(questionData.question)
// testing the buttons 'previous' and 'next'
const [btnPrevious, btnNext] = questionComponentDriver.buttonElements
expect(btnPrevious.textContent).toContain('Previous')
expect(btnNext.textContent).toContain('Next')
// testing the first AnswerDirective
expect(questionComponentDriver.firstAnswerDirective.answer).toBe(questionData.answers[0])
//testing all the AnswerDirectives
const [firstAnswer, secondAnswer] = questionComponentDriver.answerDirectives
expect(firstAnswer.answer).toBe(questionData.answers[0])
expect(secondAnswer.answer).toBe(questionData.answers[1])
expect(loggerService.log).toHaveBeenCalledWith(questionData)
})
})
})