Skip to content

Instantly share code, notes, and snippets.

@Aridian1842
Forked from Janneman96/blog-feed.component.ts
Created November 8, 2017 15:45
Show Gist options
  • Save Aridian1842/be9d5d48ffaf143e06588a2d71d2e1ac to your computer and use it in GitHub Desktop.
Save Aridian1842/be9d5d48ffaf143e06588a2d71d2e1ac to your computer and use it in GitHub Desktop.
How to unit test in Angular 2 TypeScript using Jasmine
import {BlogFeedComponent} from './blog-feed.component';
import {CoreApiService} from "../../../services/api/api.service";
import {IBlogFeed} from "./blog-feed.component.interface";
import createSpy = jasmine.createSpy;
import createSpyObj = jasmine.createSpyObj;
/**
* In the describe function, we describe what we are testing. In this case, we are testing the BlogFeedComponent.
*
* We can run all tests by running "ng test" in the terminal at the root directory of this project.
*/
describe('BlogFeedComponent', () => {
/**
* Even though providerApi and blogFeed are actually spies(also known as mocks), we do not call them providerApiSpy
* and blogFeedSpy. The usage of the spies is the same as a real object. To prevent confusion, we don't type spy
* behind the variable names.
*
* You can find more information about spies in the next comment section.
*/
let providerApi;
let blogFeed;
let blogFeedComponent: BlogFeedComponent;
/**
* The beforeEach() function is executed before every unit test. The beforeEach is typically used to initialize
* variables that are used in every unit test.
*
* ## Spy objects ##
* In this example, we use Spy objects. Spy objects are objects that mock the behaviour of real objects.
* This means that we can test a component, without testing the dependencies of the component. Instead of using the
* actual dependencies, we mock that dependency and use a fake object instead.
*
* ## Creating a spy object ##
* providerApi = createSpyObj('CoreApiService', ["query"]); creates an object of type CoreApiService.
* The fake object has got one function: query(). Now we can call the query function on this object as such:
* providerApi.query(). At the moment the function doesn't return anything. We only defined that there is a function
* called "query", but we didn't define what the function does. We will do that later.
*
* We create the blogFeed object the same way as we create the providerApi object. The blogFeed object has got
* Observable as type and has got a subscribe() function. We didn't implement the subscribe() function in
* this case, because we don't need the implementation of the subscribe() function in any of the tests in this file.
* We do need the declaration of the function, so we can call it.
*
* providerApi.query.and.returnValue(blogFeed);. In this line, we access the providerApi object and give the
* function "query" an implementation. From now on, the query() function in the providerApi object returns the
* blogFeed object.
*
* Finally we create the BlogFeedComponent and pass the fake providerApi object in its parameter. This is the
* object we are testing.
*/
beforeEach(function () {
// Arrange
providerApi = createSpyObj('CoreApiService', ['query']);
blogFeed = createSpyObj('Observable', ['subscribe']);
providerApi.query.and.returnValue(blogFeed);
blogFeedComponent = new BlogFeedComponent(providerApi);
});
/**
* The it() function is the actual unit test and it will show in the test results as a success or as a failed.
*
* The it function should read like a sentence: "It should be created".
*
* Usually we split a unit test in three parts: Arrange, Act and Assert. In arrange, we assign values to variables. In
* act we use the functionality we want to test. In assert, we check if the functionality does what we expect it to
* do. Sometimes we don't have all three parts in the function. In this case, the arrange has already been done in
* the beforeEach() function above.
*
* In this unit test, we check if the blogFeedComponent is defined. The test succeeds if the blogFeedComponent is
* defined and fails if it is undefined. It is usually a good idea to start with the simplest test we can think of
* and build up the complexity in further unit tests.
*/
it('can be created', () => {
// Assert
expect(blogFeedComponent).toBeDefined();
expect(blogFeedComponent).not.toBeNull();
});
/**
* The BlogFeedComponent has got a getRecentPosts() function that returns the most recent blog posts. In this test
* we check if the length of the blog post list the function returns is exactly as long as we expect it to be.
*
* ## The three A's: Arrange, Act, Assert ##
* In this unit test, we have a great example of the three A's: Arrange, Act and Assert. "Follow the "3As" pattern
* for unit tests: Arrange, Act, Assert. Specifically, use separate code paragraphs (groups of lines of code
* separated by a blank line) for each of the As. Arrange is variable declaration and initialization. Act is
* invoking the code under test. Assert is using the expect() functions to verify that expectations were met.
* Following this pattern consistently makes it easy to revisit test code."(source: http://bit.ly/2ujbsKt)
*
* For more information about the general idea of the three A's, i refer to this link:
* http://defragdev.com/blog/?p=783
*
* ## Arrange ##
* We initialise a fake/hardcoded blog feed. The initialising progress is not important for reading this test.
* Therefore we moved the functionality of creating the hardcoded blog feed to a separate function:
* provideDummyBlogFeed(). Next, we set the "blogs" property in the BlogFeedComponent to be the fake blog feed.
*
* ## Act ##
* We run the functionality we want to test and put the output in a variable named "recentPosts".
*
* ## Assert ##
* We expect the length of the recent posts array to be exactly 3 items long. We always want to put the actual value
* in the "expect" parameter and the expected value in the "toBe" parameter: expect(actualValue).toBe(expectedValue)
*/
it('can give three blogs when asking the most recent blogs', () => {
// Arrange
let dummyBlogFeed = provideDummyBlogFeed(6);
blogFeedComponent.blogs = dummyBlogFeed;
// Act
let recentPosts = blogFeedComponent.getRecentPosts();
// Assert
expect(recentPosts.length).toBe(3);
});
/**
* It is a good practice to test a function with multiple inputs. The function getRecentPosts gives the four newest
* posts in the above test where there are six posts in total. To make sure it keeps working with less than three
* posts, we create a test case where there is only two blogs. Here we see that it keeps working with two blogs and
* just returns the two.
*/
it('gives less than 3 blogs when asking the most recent blogs when there is less than 3 blogs available', () => {
// Arrange
blogFeedComponent.blogs = provideDummyBlogFeed(2);
// Act
let recentPosts = blogFeedComponent.getRecentPosts();
// Assert
expect(recentPosts.length).toBe(2);
});
/**
* Values like 0, null, [] and '' are some edge case values that could cause some errors in code when these edge
* cases are overlooked. When one of these values is a normal input or output value, it is a good idea to write a
* test, checking that case. In this test we see that recent posts keeps working when there are no blogs. We get an
* empty list, which is expected behaviour.
*/
it('gives an empty list when there are no recent posts', () => {
// Arrange
blogFeedComponent.blogs = provideDummyBlogFeed(0);
// Act
let recentPosts = blogFeedComponent.getRecentPosts();
// Assert
expect(recentPosts.length).toBe(0);
});
/**
* In this test we want to check if the getFeed() function in blogFeedComponent is called when we call the
* ngOnInit() function.
*
* The blogFeedComponent object is an actual object, we didn't make it a fake object like the providerApi. That
* means we don't have to define fake functions on the object, because it already has the functions.
*
* ## Arrange ##
* Because we only want to check if the function gets called, but don't want to overwrite the function, we can spy
* on the function of the object. We do that by typing: spyOn(blogFeedComponent, 'getFeed');. Here, 'getFeed' is the
* name of the function we are spying on.
*
* ## Act ##
* We call the ngOnInit() function. We don't have to put the result in a variable, because we are not testing what
* the function returns in this unit test.
*
* ## Assert ##
* In this test, we expect that the getFeed() function of the blogFeedComponent is called in the ngOnInit()
* function.
*/
it('can get the blog feed when ngOnInit is called', () => {
// Arrange
spyOn(blogFeedComponent, 'getFeed');
// Act
blogFeedComponent.ngOnInit();
// Assert
expect(blogFeedComponent.getFeed).toHaveBeenCalled();
});
/**
* ## Testing private functions ##
* Private functions are great and we don't want to make them public just to test them.
* Typescript doesn't allow us to access private functions by default. However, typescript compiles to javascript
* and javascript does allow us to access private functions. Therefore, if we can trespass the compiler and call the
* function anyways, we can test the private function without making it a public function.
*
* We can access the private function by casting the component to any as shown:
* (blogFeedComponent as any)
*
* Casting the component to any will prevent the IDE from auto completing the function you are testing, so just copy
* the function name from the component you are testing to prevent typo's.
*/
it('can provide mapped data', () => {
// Arrange
let testTitle = "this is the title";
let testBody = "this is the body";
let testModifiedBy = "this is modified by";
let testModifiedDate = "it is modified on";
let testData = [{
"Ext.Blg.Tit": testTitle,
"Ext.Blg.Bod": testBody,
"Ext.Blg.Bod!ModifiedBy": testModifiedBy,
"Ext.Blg.Bod!ModifiedDate": testModifiedDate,
}];
let expectedResult = [{
title: testTitle,
body: testBody,
modifiedBy: testModifiedBy,
modifiedDate: testModifiedDate
}];
// Act
(blogFeedComponent as any).provideBlogs(testData);
// Assert
expect(blogFeedComponent.blogs as any).toEqual(expectedResult);
});
});
/**
* This function provides a list of empty IBlogFeed mocks/spies.
*
* This function is used by unit test 'it can give three blogs when asking the most recent blogs'. This list of 5 items
* is required to split it into a list of 3 items.
*
* We decided to put this functionality in a seperate function, because the functionality is not interesting in the
* test. In the function name, we described exactly what this function does. So when you use this function in a test,
* you have a general idea of what the function does, so you don't have to read the actual implementation of the
* function.
*
* @returns {IBlogFeed[]} Array of empty IBlogFeed implementations
*/
function provideDummyBlogFeed(length: Number): IBlogFeed[] {
let blogs = [];
for (let i = 0; i < length; i++) {
blogs.push(createSpy('IBlogFeed'));
}
return blogs;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment