Repository: line/line-bot-sdk-python
Pull request #182 (2019-06-20 00:01:54)

Add json serialize test
#114 

**Main**
- add tests for serializing models

**o-ma-ke**
- add the field '**_defaultAction_**' to **ButtonTemplate** and **CarouselColumn**
- add tests for `{get/set}_rich_menu_image` and `get_message_quota`


Patch:
diff --git a/examples/flask-kitchensink/app.py b/examples/flask-kitchensink/app.py
index 48395d5..c11d447 100644
--- a/examples/flask-kitchensink/app.py
+++ b/examples/flask-kitchensink/app.py
@@ -52,11 +52,8 @@ app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1, x_proto=1)
 # get channel_secret and channel_access_token from your environment variable
 channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
 channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
-if channel_secret is None:
-    print('Specify LINE_CHANNEL_SECRET as environment variable.')
-    sys.exit(1)
-if channel_access_token is None:
-    print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
+if channel_secret is None or channel_access_token is None:
+    print('Specify LINE_CHANNEL_SECRET and LINE_CHANNEL_ACCESS_TOKEN as environment variables.')
     sys.exit(1)
 
 line_bot_api = LineBotApi(channel_access_token)
@@ -379,7 +376,7 @@ def handle_location_message(event):
     line_bot_api.reply_message(
         event.reply_token,
         LocationSendMessage(
-            title=event.message.title, address=event.message.address,
+            title='Location', address=event.message.address,
             latitude=event.message.latitude, longitude=event.message.longitude
         )
     )
@@ -445,13 +442,14 @@ def handle_file_message(event):
 
 @handler.add(FollowEvent)
 def handle_follow(event):
+    app.logger.info("Got Follow event:" + event.source.user_id)
     line_bot_api.reply_message(
         event.reply_token, TextSendMessage(text='Got follow event'))
 
 
 @handler.add(UnfollowEvent)
-def handle_unfollow():
-    app.logger.info("Got Unfollow event")
+def handle_unfollow(event):
+    app.logger.info("Got Unfollow event:" + event.source.user_id)
 
 
 @handler.add(JoinEvent)
diff --git a/linebot/api.py b/linebot/api.py
index e8bbacd..c345e75 100644
--- a/linebot/api.py
+++ b/linebot/api.py
@@ -62,7 +62,7 @@ class LineBotApi(object):
     def reply_message(self, reply_token, messages, notification_disabled=False, timeout=None):
         """Call reply message API.
 
-        https://devdocs.line.me/en/#reply-message
+        https://developers.line.biz/en/reference/messaging-api/#send-reply-message
 
         Respond to events from users, groups, and rooms.
 
@@ -103,7 +103,7 @@ class LineBotApi(object):
     def push_message(self, to, messages, notification_disabled=False, timeout=None):
         """Call push message API.
 
-        https://devdocs.line.me/en/#push-message
+        https://developers.line.biz/en/reference/messaging-api/#send-push-message
 
         Send messages to users, groups, and rooms at any time.
 
@@ -136,7 +136,7 @@ class LineBotApi(object):
     def multicast(self, to, messages, notification_disabled=False, timeout=None):
         """Call multicast API.
 
-        https://devdocs.line.me/en/#multicast
+        https://developers.line.biz/en/reference/messaging-api/#send-multicast-message
 
         Send messages to multiple users at any time.
 
@@ -223,7 +223,7 @@ class LineBotApi(object):
     def get_profile(self, user_id, timeout=None):
         """Call get profile API.
 
-        https://devdocs.line.me/en/#bot-api-get-profile
+        https://developers.line.biz/en/reference/messaging-api/#get-profile
 
         Get user profile information.
 
@@ -246,7 +246,7 @@ class LineBotApi(object):
     def get_group_member_profile(self, group_id, user_id, timeout=None):
         """Call get group member profile API.
 
-        https://devdocs.line.me/en/#get-group-room-member-profile
+        https://developers.line.biz/en/reference/messaging-api/#get-group-member-profile
 
         Gets the user profile of a member of a group that
         the bot is in. This can be the user ID of a user who has
@@ -272,7 +272,7 @@ class LineBotApi(object):
     def get_room_member_profile(self, room_id, user_id, timeout=None):
         """Call get room member profile API.
 
-        https://devdocs.line.me/en/#get-group-room-member-profile
+        https://developers.line.biz/en/reference/messaging-api/#get-room-member-profile
 
         Gets the user profile of a member of a room that
         the bot is in. This can be the user ID of a user who has
@@ -298,7 +298,7 @@ class LineBotApi(object):
     def get_group_member_ids(self, group_id, start=None, timeout=None):
         """Call get group member IDs API.
 
-        https://devdocs.line.me/en/#get-group-room-member-ids
+        https://developers.line.biz/en/reference/messaging-api/#get-group-member-ids
 
         Gets the user IDs of the members of a group that the bot is in.
         This includes the user IDs of users who have not added the bot as a friend
@@ -327,7 +327,7 @@ class LineBotApi(object):
     def get_room_member_ids(self, room_id, start=None, timeout=None):
         """Call get room member IDs API.
 
-        https://devdocs.line.me/en/#get-group-room-member-ids
+        https://developers.line.biz/en/reference/messaging-api/#get-room-member-ids
 
         Gets the user IDs of the members of a group that the bot is in.
         This includes the user IDs of users who have not added the bot as a friend
@@ -356,7 +356,7 @@ class LineBotApi(object):
     def get_message_content(self, message_id, timeout=None):
         """Call get content API.
 
-        https://devdocs.line.me/en/#get-content
+        https://developers.line.biz/en/reference/messaging-api/#get-content
 
         Retrieve image, video, and audio data sent by users.
 
@@ -379,7 +379,7 @@ class LineBotApi(object):
     def leave_group(self, group_id, timeout=None):
         """Call leave group API.
 
-        https://devdocs.line.me/en/#leave
+        https://developers.line.biz/en/reference/messaging-api/#leave-group
 
         Leave a group.
 
@@ -398,7 +398,7 @@ class LineBotApi(object):
     def leave_room(self, room_id, timeout=None):
         """Call leave room API.
 
-        https://devdocs.line.me/en/#leave
+        https://developers.line.biz/en/reference/messaging-api/#leave-room
 
         Leave a room.
 
diff --git a/linebot/models/error.py b/linebot/models/error.py
index 557b77f..20db5ad 100644
--- a/linebot/models/error.py
+++ b/linebot/models/error.py
@@ -22,7 +22,7 @@ from .base import Base
 class Error(Base):
     """Error response of LINE messaging API.
 
-    https://devdocs.line.me/en/#error-response
+    https://developers.line.biz/en/reference/messaging-api/#error-response
     """
 
     def __init__(self, message=None, details=None, **kwargs):
@@ -47,16 +47,11 @@ class Error(Base):
 
 
 class ErrorDetail(Base):
-    """ErrorDetail response of LINE messaging API.
-
-    https://devdocs.line.me/en/#error-response
-    """
+    """ErrorDetail response of LINE messaging API."""
 
     def __init__(self, message=None, property=None, **kwargs):
         """__init__ method.
 
-        https://devdocs.line.me/en/#error-response
-
         :param str message: Details of the error message
         :param str property: Related property
         :param kwargs:
diff --git a/linebot/models/events.py b/linebot/models/events.py
index dbcd801..90bd3ca 100644
--- a/linebot/models/events.py
+++ b/linebot/models/events.py
@@ -36,13 +36,13 @@ from linebot.models.things import (
     DeviceLink,
     ScenarioResult,
 )
-from linebot.models.things import Things  # noqa
+from linebot.models.things import Things  # noqa, backward compatibility
 
 
 class Event(with_metaclass(ABCMeta, Base)):
     """Abstract Base Class of Webhook Event.
 
-    https://devdocs.line.me/en/#webhook-event-object
+    https://developers.line.biz/en/reference/messaging-api/#webhook-event-objects
     """
 
     def __init__(self, timestamp=None, source=None, **kwargs):
@@ -69,7 +69,7 @@ class Event(with_metaclass(ABCMeta, Base)):
 class MessageEvent(Event):
     """Webhook MessageEvent.
 
-    https://devdocs.line.me/en/#message-event
+    https://developers.line.biz/en/reference/messaging-api/#message-event
 
     Event object which contains the sent message.
     The message field contains a message object which corresponds with the message type.
@@ -109,7 +109,7 @@ class MessageEvent(Event):
 class FollowEvent(Event):
     """Webhook FollowEvent.
 
-    https://devdocs.line.me/en/#follow-event
+    https://developers.line.biz/en/reference/messaging-api/#follow-event
 
     Event object for when your account is added as a friend (or unblocked).
     You can reply to follow events.
@@ -135,7 +135,7 @@ class FollowEvent(Event):
 class UnfollowEvent(Event):
     """Webhook UnfollowEvent.
 
-    https://devdocs.line.me/en/#unfollow-event
+    https://developers.line.biz/en/reference/messaging-api/#unfollow-event
 
     Event object for when your account is blocked.
     """
@@ -158,7 +158,7 @@ class UnfollowEvent(Event):
 class JoinEvent(Event):
     """Webhook JoinEvent.
 
-    https://devdocs.line.me/en/#join-event
+    https://developers.line.biz/en/reference/messaging-api/#join-event
 
     Event object for when your account joins a group or talk room.
     You can reply to join events.
@@ -184,7 +184,7 @@ class JoinEvent(Event):
 class LeaveEvent(Event):
     """Webhook LeaveEvent.
 
-    https://devdocs.line.me/en/#leave-event
+    https://developers.line.biz/en/reference/messaging-api/#leave-event
 
     Event object for when your account leaves a group.
     """
@@ -207,7 +207,7 @@ class LeaveEvent(Event):
 class PostbackEvent(Event):
     """Webhook PostbackEvent.
 
-    https://devdocs.line.me/en/#postback-event
+    https://developers.line.biz/en/reference/messaging-api/#postback-event
 
     Event object for when a user performs an action on
     a template message which initiates a postback.
@@ -239,7 +239,7 @@ class PostbackEvent(Event):
 class BeaconEvent(Event):
     """Webhook BeaconEvent.
 
-    https://devdocs.line.me/en/#beacon-event
+    https://developers.line.biz/en/reference/messaging-api/#beacon-event
 
     Event object for when a user detects a LINE Beacon. You can reply to beacon events.
     """
@@ -400,7 +400,7 @@ class ThingsEvent(Event):
 class Postback(Base):
     """Postback.
 
-    https://devdocs.line.me/en/#postback-event
+    https://developers.line.biz/en/reference/messaging-api/#postback-event
     """
 
     def __init__(self, data=None, params=None, **kwargs):
@@ -421,7 +421,7 @@ class Postback(Base):
 class Beacon(Base):
     """Beacon.
 
-    https://devdocs.line.me/en/#beacon-event
+    https://developers.line.biz/en/reference/messaging-api/#beacon-event
     """
 
     def __init__(self, type=None, hwid=None, dm=None, **kwargs):
diff --git a/linebot/models/imagemap.py b/linebot/models/imagemap.py
index 1548684..75d5415 100644
--- a/linebot/models/imagemap.py
+++ b/linebot/models/imagemap.py
@@ -77,14 +77,12 @@ class ImagemapSendMessage(SendMessage):
 class BaseSize(Base):
     """BaseSize.
 
-    https://devdocs.line.me/en/#imagemap-message
+    https://developers.line.biz/en/reference/messaging-api/#imagemap-message
     """
 
     def __init__(self, width=None, height=None, **kwargs):
         """__init__ method.
 
-        https://devdocs.line.me/en/#imagemap-message
-
         :param int width: Width of base image (set to 1040px）
         :param int height: Height of base image（set to the height
             that corresponds to a width of 1040px
@@ -99,7 +97,7 @@ class BaseSize(Base):
 class ImagemapAction(with_metaclass(ABCMeta, Base)):
     """ImagemapAction.
 
-    https://devdocs.line.me/en/#imagemap-message
+    https://developers.line.biz/en/reference/messaging-api/#imagemap-message
     """
 
     def __init__(self, **kwargs):
@@ -115,7 +113,7 @@ class ImagemapAction(with_metaclass(ABCMeta, Base)):
 class URIImagemapAction(ImagemapAction):
     """URIImagemapAction.
 
-    https://devdocs.line.me/en/#imagemap-message
+    https://developers.line.biz/en/reference/messaging-api/#imagemap-message
     """
 
     def __init__(self, link_uri=None, area=None, **kwargs):
@@ -136,7 +134,7 @@ class URIImagemapAction(ImagemapAction):
 class MessageImagemapAction(ImagemapAction):
     """MessageImagemapAction.
 
-    https://devdocs.line.me/en/#imagemap-message
+    https://developers.line.biz/en/reference/messaging-api/#imagemap-message
     """
 
     def __init__(self, text=None, area=None, **kwargs):
@@ -157,7 +155,7 @@ class MessageImagemapAction(ImagemapAction):
 class ImagemapArea(Base):
     """ImagemapArea.
 
-    https://devdocs.line.me/en/#imagemap-area-object
+    https://developers.line.biz/en/reference/messaging-api/#imagemap-area-object
 
     Defines the size of the full imagemap with the width as 1040px.
     The top left is used as the origin of the area.
diff --git a/linebot/models/responses.py b/linebot/models/responses.py
index 180f4d0..19b75f8 100644
--- a/linebot/models/responses.py
+++ b/linebot/models/responses.py
@@ -23,7 +23,7 @@ from .rich_menu import RichMenuSize, RichMenuArea
 class Profile(Base):
     """Profile.
 
-    https://devdocs.line.me/en/#bot-api-get-profile
+    https://developers.line.biz/en/reference/messaging-api/#get-profile
     """
 
     def __init__(self, display_name=None, user_id=None, picture_url=None,
@@ -47,7 +47,8 @@ class Profile(Base):
 class MemberIds(Base):
     """MemberIds.
 
-    https://devdocs.line.me/en/#get-group-room-member-ids
+    https://developers.line.biz/en/reference/messaging-api/#get-group-member-user-ids
+    https://developers.line.biz/en/reference/messaging-api/#get-room-member-user-ids
     """
 
     def __init__(self, member_ids=None, next=None, **kwargs):
@@ -69,7 +70,7 @@ class MemberIds(Base):
 class Content(object):
     """MessageContent.
 
-    https://devdocs.line.me/ja/#get-content
+    https://developers.line.biz/en/reference/messaging-api/#get-content
     """
 
     def __init__(self, response):
diff --git a/linebot/models/send_messages.py b/linebot/models/send_messages.py
index 855e03d..3caf0f1 100644
--- a/linebot/models/send_messages.py
+++ b/linebot/models/send_messages.py
@@ -43,7 +43,7 @@ class SendMessage(with_metaclass(ABCMeta, Base)):
 class TextSendMessage(SendMessage):
     """TextSendMessage.
 
-    https://devdocs.line.me/en/#text
+    https://developers.line.biz/en/reference/messaging-api/#text-message
     """
 
     def __init__(self, text=None, quick_reply=None, **kwargs):
@@ -63,7 +63,7 @@ class TextSendMessage(SendMessage):
 class ImageSendMessage(SendMessage):
     """ImageSendMessage.
 
-    https://devdocs.line.me/en/#image
+    https://developers.line.biz/en/reference/messaging-api/#image-message
     """
 
     def __init__(self, original_content_url=None, preview_image_url=None,
@@ -94,7 +94,7 @@ class ImageSendMessage(SendMessage):
 class VideoSendMessage(SendMessage):
     """VideoSendMessage.
 
-    https://devdocs.line.me/en/#video
+    https://developers.line.biz/en/reference/messaging-api/#video-message
     """
 
     def __init__(self, original_content_url=None, preview_image_url=None,
@@ -102,15 +102,9 @@ class VideoSendMessage(SendMessage):
         """__init__ method.
 
         :param str original_content_url: URL of video file.
-            HTTPS
-            mp4
-            Less than 1 minute
-            Max: 10 MB
+            HTTPS. mp4. Less than 1 minute. Max: 10 MB.
         :param str preview_image_url: URL of preview image.
-            HTTPS
-            JPEG
-            Max: 240 x 240
-            Max: 1 MB
+            HTTPS. JPEG. Max: 240 x 240. Max: 1 MB.
         :param quick_reply: QuickReply object
         :type quick_reply: T <= :py:class:`linebot.models.send_messages.QuickReply`
         :param kwargs:
@@ -125,17 +119,14 @@ class VideoSendMessage(SendMessage):
 class AudioSendMessage(SendMessage):
     """AudioSendMessage.
 
-    https://devdocs.line.me/en/#audio
+    https://developers.line.biz/en/reference/messaging-api/#audio-message
     """
 
     def __init__(self, original_content_url=None, duration=None, quick_reply=None, **kwargs):
         """__init__ method.
 
-        :param str original_content_url: URL of audio file.
-            HTTPS
-            m4a
-            Less than 1 minute
-            Max 10 MB
+        :param str original_content_url: URL of audio file. HTTPS.
+            m4a. Less than 1 minute. Max 10 MB.
         :param long duration: Length of audio file (milliseconds).
         :param quick_reply: QuickReply object
         :type quick_reply: T <= :py:class:`linebot.models.send_messages.QuickReply`
@@ -151,7 +142,7 @@ class AudioSendMessage(SendMessage):
 class LocationSendMessage(SendMessage):
     """LocationSendMessage.
 
-    https://devdocs.line.me/en/#location
+    https://developers.line.biz/en/reference/messaging-api/#location-message
     """
 
     def __init__(self, title=None, address=None, latitude=None, longitude=None,
@@ -178,7 +169,7 @@ class LocationSendMessage(SendMessage):
 class StickerSendMessage(SendMessage):
     """StickerSendMessage.
 
-    https://devdocs.line.me/en/#sticker
+    https://developers.line.biz/en/reference/messaging-api/#sticker-message
     """
 
     def __init__(self, package_id=None, sticker_id=None, quick_reply=None, **kwargs):
@@ -200,7 +191,7 @@ class StickerSendMessage(SendMessage):
 class QuickReply(with_metaclass(ABCMeta, Base)):
     """QuickReply.
 
-    https://developers.line.me/en/docs/messaging-api/using-quick-reply/
+    https://developers.line.me/en/docs/messaging-api/#quick-reply
     """
 
     def __init__(self, items=None, **kwargs):
@@ -224,7 +215,7 @@ class QuickReply(with_metaclass(ABCMeta, Base)):
 class QuickReplyButton(with_metaclass(ABCMeta, Base)):
     """QuickReplyButton.
 
-    https://developers.line.me/en/reference/messaging-api/#items-object
+    https://developers.line.me/en/reference/messaging-api/#quick-reply-button-object
     """
 
     def __init__(self, image_url=None, action=None, **kwargs):
diff --git a/linebot/models/sources.py b/linebot/models/sources.py
index fa70525..3671abd 100644
--- a/linebot/models/sources.py
+++ b/linebot/models/sources.py
@@ -54,7 +54,7 @@ class Source(with_metaclass(ABCMeta, Base)):
 class SourceUser(Source):
     """SourceUser.
 
-    https://devdocs.line.me/en/#source-user
+    https://developers.line.biz/en/reference/messaging-api/#source-user
 
     JSON object which contains the source user of the event.
     """
@@ -85,7 +85,7 @@ class SourceUser(Source):
 class SourceGroup(Source):
     """SourceGroup.
 
-    https://devdocs.line.me/en/#source-group
+    https://developers.line.biz/en/reference/messaging-api/#source-group
 
     JSON object which contains the source group of the event.
     """
@@ -118,7 +118,7 @@ class SourceGroup(Source):
 class SourceRoom(Source):
     """SourceRoom.
 
-    https://devdocs.line.me/en/#source-room
+    https://developers.line.biz/en/reference/messaging-api/#source-room
 
     JSON object which contains the source room of the event.
     """
diff --git a/linebot/models/template.py b/linebot/models/template.py
index 0e8efb5..00da4ef 100644
--- a/linebot/models/template.py
+++ b/linebot/models/template.py
@@ -28,7 +28,7 @@ from .send_messages import SendMessage
 class TemplateSendMessage(SendMessage):
     """TemplateSendMessage.
 
-    https://devdocs.line.me/en/#template-messages
+    https://developers.line.biz/en/reference/messaging-api/#template-messages
 
     Template messages are messages with predefined layouts which you can customize.
     There are three types of templates available
@@ -73,7 +73,7 @@ class Template(with_metaclass(ABCMeta, Base)):
 class ButtonsTemplate(Template):
     """ButtonsTemplate.
 
-    https://devdocs.line.me/en/#buttons
+    https://developers.line.biz/en/reference/messaging-api/#buttons
 
     Template message with an image, title, text, and multiple action buttons.
     """
@@ -81,7 +81,7 @@ class ButtonsTemplate(Template):
     def __init__(self, text=None, title=None, thumbnail_image_url=None,
                  image_aspect_ratio=None,
                  image_size=None, image_background_color=None,
-                 actions=None, **kwargs):
+                 actions=None, default_action=None, **kwargs):
         """__init__ method.
 
         :param str text: Message text.
@@ -99,18 +99,15 @@ class ButtonsTemplate(Template):
             Specify one of the following values:
             rectangle: 1.51:1
             square: 1:1
-        :param str image_size: Size of the image.
-            Specify one of the following values:
-            cover: The image fills the entire image area.
-                Parts of the image that do not fit in the area are not displayed.
-            contain: The entire image is displayed in the image area.
-                A background is displayed in the unused areas to the left and right
-                of vertical images and in the areas above and below horizontal images.
+        :param str image_size: Size of the image. Specify ``cover`` or ``contain``.
         :param str image_background_color: Background color of image.
             Specify a RGB color value.
         :param actions: Action when tapped.
             Max: 4
         :type actions: list[T <= :py:class:`linebot.models.actions.Action`]
+        :param default_action: Action when image is tapped;
+            set for the entire image, title, and text area
+        :type default_action: T <= :py:class:`linebot.models.actions.Action`
         :param kwargs:
         """
         super(ButtonsTemplate, self).__init__(**kwargs)
@@ -123,12 +120,13 @@ class ButtonsTemplate(Template):
         self.image_size = image_size
         self.image_background_color = image_background_color
         self.actions = get_actions(actions)
+        self.default_action = get_action(default_action)
 
 
 class ConfirmTemplate(Template):
     """ConfirmTemplate.
 
-    https://devdocs.line.me/en/#confirm
+    https://developers.line.biz/en/reference/messaging-api/#confirm
 
     Template message with two action buttons.
     """
@@ -153,7 +151,7 @@ class ConfirmTemplate(Template):
 class CarouselTemplate(Template):
     """CarouselTemplate.
 
-    https://devdocs.line.me/en/#carousel
+    https://developers.line.biz/en/reference/messaging-api/#carousel
 
     Template message with multiple columns which can be cycled like a carousel.
     """
@@ -166,16 +164,8 @@ class CarouselTemplate(Template):
             Max: 10
         :type columns: list[T <= :py:class:`linebot.models.template.CarouselColumn`]
         :param str image_aspect_ratio: Aspect ratio of the image.
-            Specify one of the following values:
-            rectangle: 1.51:1
-            square: 1:1
-        :param str image_size: Size of the image.
-            Specify one of the following values:
-            cover: The image fills the entire image area.
-                Parts of the image that do not fit in the area are not displayed.
-            contain: The entire image is displayed in the image area.
-                A background is displayed in the unused areas to the left and right
-                of vertical images and in the areas above and below horizontal images.
+            Specify ``rectangle`` or ``square``.
+        :param str image_size: Size of the image. Specify ``cover`` or ``contain``.
         :param kwargs:
         """
         super(CarouselTemplate, self).__init__(**kwargs)
@@ -196,7 +186,7 @@ class CarouselTemplate(Template):
 class ImageCarouselTemplate(Template):
     """ImageCarouselTemplate.
 
-    https://devdocs.line.me/en/#image-carousel
+    https://developers.line.biz/en/reference/messaging-api/#image-carousel
 
     Template message with multiple images columns which can be cycled like as carousel.
     """
@@ -225,11 +215,12 @@ class ImageCarouselTemplate(Template):
 class CarouselColumn(Base):
     """CarouselColumn.
 
-    https://devdocs.line.me/en/#column-object
+    https://developers.line.biz/en/reference/messaging-api/#column-object
     """
 
-    def __init__(self, text=None, title=None, thumbnail_image_url=None,
-                 image_background_color=None, actions=None, **kwargs):
+    def __init__(self, text=None, title=None,
+                 thumbnail_image_url=None, image_background_color=None,
+                 actions=None, default_action=None, **kwargs):
         """__init__ method.
 
         :param str text: Message text.
@@ -237,17 +228,16 @@ class CarouselColumn(Base):
             Max: 60 characters (message with an image or title)
         :param str title: Title.
             Max: 40 characters
-        :param str thumbnail_image_url: Image URL.
-            HTTPS
-            JPEG or PNG
-            Aspect ratio: 1:1.51
-            Max width: 1024px
-            Max: 1 MB
+        :param str thumbnail_image_url: Image URL (HTTPS). JPEG or PNG. Aspect ration is 1:1.51.
+            Max width: 1024px, Max: 1 MB.
         :param str image_background_color: Background color of image.
             Specify a RGB color value.
         :param actions: Action when tapped.
             Max: 3
         :type actions: list[T <= :py:class:`linebot.models.actions.Action`]
+        :param default_action: Action when image is tapped;
+            set for the entire image, title, and text area
+        :type default_action: T <= :py:class:`linebot.models.actions.Action`
         :param kwargs:
         """
         super(CarouselColumn, self).__init__(**kwargs)
@@ -257,25 +247,21 @@ class CarouselColumn(Base):
         self.thumbnail_image_url = thumbnail_image_url
         self.image_background_color = image_background_color
         self.actions = get_actions(actions)
+        self.default_action = get_action(default_action)
 
 
 class ImageCarouselColumn(Base):
     """ImageCarouselColumn.
 
-    https://devdocs.line.me/en/#column-object-for-image-carousel
+    https://developers.line.biz/en/reference/messaging-api/#column-object-for-image-carousel
     """
 
     def __init__(self, image_url=None, action=None, **kwargs):
         """__init__ method.
 
-        :param str image_url: Image URL.
-            HTTPS
-            JPEG or PNG
-            Aspect ratio: 1:1
-            Max width: 1024px
-            Max: 1 MB
-        :param action: Action when image is tapped
-            Max: 5
+        :param str image_url: Image URL (HTTPS). JPEG or PNG. Aspect ration is 1:1.
+            Max width: 1024px, Max: 1 MB.
+        :param action: Action when image is tapped. Max: 5.
         :type action: T <= :py:class:`linebot.models.actions.Action`
         :param kwargs:
         """
diff --git a/linebot/webhook.py b/linebot/webhook.py
index 29eb085..f9d1f0f 100644
--- a/linebot/webhook.py
+++ b/linebot/webhook.py
@@ -73,7 +73,7 @@ else:
 class SignatureValidator(object):
     """Signature validator.
 
-    https://devdocs.line.me/en/#webhook-authentication
+    https://developers.line.biz/en/reference/messaging-api/#signature-validation
     """
 
     def __init__(self, channel_secret):
@@ -86,12 +86,9 @@ class SignatureValidator(object):
     def validate(self, body, signature):
         """Check signature.
 
-        https://devdocs.line.me/en/#webhook-authentication
-
         :param str body: Request body (as text)
         :param str signature: X-Line-Signature value (as text)
         :rtype: bool
-        :return: result
         """
         gen_signature = hmac.new(
             self.channel_secret,


Test patch:
diff --git a/tests/api/test_get_message_quota.py b/tests/api/test_get_message_quota.py
new file mode 100644
index 0000000..8192e9a
--- /dev/null
+++ b/tests/api/test_get_message_quota.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the "License"); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+
+import responses
+
+from linebot import (
+    LineBotApi
+)
+
+
+class TestLineBotApi(unittest.TestCase):
+    def setUp(self):
+        self.tested = LineBotApi('channel_secret')
+
+    @responses.activate
+    def test_get_message_quota(self):
+        responses.add(
+            responses.GET,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/message/quota',
+            json={
+                'type': 'limited',
+                'value': 1000
+            },
+            status=200
+        )
+        res = self.tested.get_message_quota()
+        request = responses.calls[0].request
+        self.assertEqual('GET', request.method)
+        self.assertEqual('limited', res.type)
+        self.assertEqual(1000, res.value)
+
+    @responses.activate
+    def test_get_message_quota_2(self):
+        responses.add(
+            responses.GET,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/message/quota',
+            json={'type': 'none'},
+            status=200
+        )
+        res = self.tested.get_message_quota()
+        request = responses.calls[0].request
+        self.assertEqual('GET', request.method)
+        self.assertEqual('none', res.type)
+
+    @responses.activate
+    def test_get_message_quota_consumption(self):
+        responses.add(
+            responses.GET,
+            LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/message/quota/consumption',
+            json={'totalUsage': 500},
+            status=200
+        )
+        res = self.tested.get_message_quota_consumption()
+        request = responses.calls[0].request
+        self.assertEqual('GET', request.method)
+        self.assertEqual(500, res.total_usage)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/api/test_rich_menu.py b/tests/api/test_rich_menu.py
index 383758f..b770c63 100644
--- a/tests/api/test_rich_menu.py
+++ b/tests/api/test_rich_menu.py
@@ -388,6 +388,56 @@ class TestLineBotApi(unittest.TestCase):
             LineBotApi.DEFAULT_API_ENDPOINT + '/v2/bot/user/all/richmenu'
         )
 
+    @responses.activate
+    def test_get_rich_menu_image(self):
+        rich_menu_id = '1234'
+        body = b'hogieoidksk'
+        responses.add(
+            responses.GET,
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/richmenu/{rich_menu_id}/content'.format(rich_menu_id=rich_menu_id),
+            body=body, status=200
+        )
+
+        res = self.tested.get_rich_menu_image(rich_menu_id)
+
+        request = responses.calls[0].request
+        self.assertEqual(request.method, 'GET')
+        self.assertEqual(
+            request.url,
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/richmenu/{rich_menu_id}/content'.format(rich_menu_id=rich_menu_id),
+        )
+        self.assertEqual(
+            body,
+            res.content
+        )
+
+    @responses.activate
+    def test_set_rich_menu_image(self):
+        rich_menu_id = '1234'
+        body = b'hogieoidksk'
+        responses.add(
+            responses.POST,
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/richmenu/{rich_menu_id}/content'.format(rich_menu_id=rich_menu_id),
+            json={}, status=200
+        )
+
+        self.tested.set_rich_menu_image(
+            rich_menu_id=rich_menu_id,
+            content_type='image/jpeg',
+            content=body
+        )
+
+        request = responses.calls[0].request
+        self.assertEqual('POST', request.method)
+        self.assertEqual(
+            LineBotApi.DEFAULT_API_ENDPOINT +
+            '/v2/bot/richmenu/{rich_menu_id}/content'.format(rich_menu_id=rich_menu_id),
+            request.url
+        )
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/models/serialize_test_case.py b/tests/models/serialize_test_case.py
new file mode 100644
index 0000000..a9dee1e
--- /dev/null
+++ b/tests/models/serialize_test_case.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the "License"); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import sys
+import unittest
+from numbers import Number
+
+from linebot.models import (
+    Base,
+)
+from linebot.utils import to_camel_case
+
+PY3 = sys.version_info[0] == 3
+
+
+class SerializeTestCase(unittest.TestCase):
+    MESSAGE = 'message'
+    STICKER = 'sticker'
+    POSTBACK = 'postback'
+    CAMERA = 'camera'
+    CAMERA_ROLL = 'cameraRoll'
+    DATETIMEPICKER = 'datetimepicker'
+    URI = 'uri'
+    LOCATION = 'location'
+    FLEX = 'flex'
+    SPACER = 'spacer'
+    BUBBLE = 'bubble'
+    CAROUSEL = 'carousel'
+    BOX = 'box'
+    BUTTON = 'button'
+    FILLER = 'filler'
+    ICON = 'icon'
+    TEXT = 'text'
+    IMAGE = 'image'
+    VIDEO = 'video'
+    AUDIO = 'audio'
+    SEPARATOR = 'separator'
+    IMAGEMAP = 'imagemap'
+    ACTION = 'action'
+    TEMPLATE = 'template'
+    BUTTONS = 'buttons'
+    CONFIRM = 'confirm'
+    IMAGE_CAROUSEL = 'image_carousel'
+
+    def serialize_as_dict(self, obj, type=None):
+        if isinstance(obj, Base):
+            return obj.as_json_dict()
+        elif isinstance(obj, dict):
+            ret = {to_camel_case(k): self.serialize_as_dict(v) for k, v in obj.items()}
+            if type is not None:
+                ret['type'] = type
+            return ret
+        elif isinstance(obj, list):
+            return [self.serialize_as_dict(elem) for elem in obj]
+        else:
+            if PY3:
+                self.assertIsInstance(obj, (str, bool, Number))
+            else:
+                self.assertIsInstance(obj, (basestring, bool, Number))  # noqa
+            return obj
+
+    class ConstError(TypeError):
+        pass
+
+    def __setattr__(self, name, value):
+        if name in SerializeTestCase.__dict__:
+            raise self.ConstError("Can't rebind const (%s)" % name)
+        self.__dict__[name] = value
diff --git a/tests/models/test_actions.py b/tests/models/test_actions.py
new file mode 100644
index 0000000..6305488
--- /dev/null
+++ b/tests/models/test_actions.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the "License"); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+
+from linebot.models import (
+    PostbackAction,
+    MessageAction,
+    CameraRollAction,
+    CameraAction,
+    URIAction,
+    LocationAction,
+    DatetimePickerAction,
+    AltUri,
+)
+from tests.models.serialize_test_case import SerializeTestCase
+
+
+class TestActions(SerializeTestCase):
+    def test_postback(self):
+        arg = {
+            'label': 'Buy',
+            'data': 'action=buy&id=1',
+            'display_text': 'buy'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.POSTBACK),
+            PostbackAction(**arg).as_json_dict()
+        )
+
+    def test_message(self):
+        arg = {
+            'label': 'Yes',
+            'text': 'yes'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.MESSAGE),
+            MessageAction(**arg).as_json_dict()
+        )
+
+    def test_camera(self):
+        arg = {
+            'label': 'camera'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.CAMERA),
+            CameraAction(**arg).as_json_dict()
+        )
+
+    def test_camera_roll(self):
+        arg = {
+            'label': 'camera roll'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.CAMERA_ROLL),
+            CameraRollAction(**arg).as_json_dict()
+        )
+
+    def test_datetime_picker(self):
+        arg = {
+            'label': 'Select date',
+            'data': 'storeId=12345',
+            'mode': 'datetime',
+            'initial': '2017-12-25t00:00',
+            'max': '2018-01-24t23:59',
+            'min': '2017-12-25t00:00'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.DATETIMEPICKER),
+            DatetimePickerAction(**arg).as_json_dict()
+        )
+
+    def test_uri(self):
+        arg = {
+            'label': 'View detail',
+            'uri': 'https://example.com',
+            'alt_uri': AltUri(desktop='https://example.com')
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.URI),
+            URIAction(**arg).as_json_dict()
+        )
+
+    def test_location(self):
+        arg = {
+            'label': 'Location'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.LOCATION),
+            LocationAction(**arg).as_json_dict()
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/models/test_flex_message.py b/tests/models/test_flex_message.py
new file mode 100644
index 0000000..ac01108
--- /dev/null
+++ b/tests/models/test_flex_message.py
@@ -0,0 +1,225 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the 'License'); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+
+from linebot.models import (
+    FlexSendMessage,
+    BlockStyle,
+    BubbleStyle,
+    BubbleContainer,
+    CarouselContainer,
+    BoxComponent,
+    TextComponent,
+    SeparatorComponent,
+    ImageComponent,
+    ButtonComponent,
+    FillerComponent,
+    IconComponent,
+    SpacerComponent,
+    URIAction,
+)
+from tests.models.serialize_test_case import SerializeTestCase
+
+
+class TestFlexMessage(SerializeTestCase):
+    def test_flex_message(self):
+        arg = {
+            'alt_text': 'this is a flex message',
+            'contents':
+                BubbleContainer(
+                    body=BoxComponent(
+                        layout='vertical',
+                        contents=[
+                            TextComponent(text='hello'),
+                            TextComponent(text='world')
+                        ]
+                    )
+                )
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.FLEX),
+            FlexSendMessage(**arg).as_json_dict()
+        )
+
+    def test_bubble_container(self):
+        arg = {
+            'header':
+                BoxComponent(layout='vertical',
+                             contents=[TextComponent(text='Header text')]),
+            'hero':
+                ImageComponent(uri='https://example.com/flex/images/image.jpg'),
+            'body':
+                BoxComponent(layout='vertical',
+                             contents=[TextComponent(text='Body text')]),
+            'footer':
+                BoxComponent(layout='vertical',
+                             contents=[TextComponent(text='Footer text')]),
+            'styles':
+                BubbleStyle(
+                    header=BlockStyle(background_color='#00ffff'),
+                    hero=BlockStyle(background_color='#00ffff',
+                                    separator=True),
+                    footer=BlockStyle(background_color='#00ffff',
+                                      separator=True,
+                                      separator_color='#00ffff')
+                )
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.BUBBLE),
+            BubbleContainer(**arg).as_json_dict()
+        )
+
+    def test_bubble_style(self):
+        arg = {
+            'header':
+                BlockStyle(background_color='#00ffff'),
+            'hero':
+                BlockStyle(background_color='#00ffff',
+                           separator=True),
+            'footer':
+                BlockStyle(background_color='#00ffff',
+                           separator=True,
+                           separator_color='#00ffff')
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            BubbleStyle(**arg).as_json_dict()
+        )
+
+    def test_block_style(self):
+        arg = {
+            'background_color': '#00ffff',
+            'separator': True,
+            'separator_color': '#000000'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            BlockStyle(**arg).as_json_dict()
+        )
+
+    def test_carousel_container(self):
+        arg = {
+            'contents': [
+                BubbleContainer(
+                    body=BoxComponent(
+                        layout='vertical',
+                        contents=[TextComponent(text='Hey')]
+                    )
+                ),
+                BubbleContainer(
+                    body=BoxComponent(
+                        layout='vertical',
+                        contents=[TextComponent(text='Foo')]
+                    )
+                ),
+            ]
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.CAROUSEL),
+            CarouselContainer(**arg).as_json_dict()
+        )
+
+    def test_box_component(self):
+        arg = {
+            'layout': 'vertical',
+            'contents': [
+                ImageComponent(url='https://example.com/flex/images/image.jpg'),
+                SeparatorComponent(),
+                TextComponent(text='Text in the box'),
+            ]
+        }
+
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.BOX),
+            BoxComponent(**arg).as_json_dict()
+        )
+
+    def test_button_component(self):
+        arg = {
+            'action':
+                URIAction(label='Tap me',
+                          uri='https://example.com'),
+            'style': 'primary',
+            'color': '#0000ff'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.BUTTON),
+            ButtonComponent(**arg).as_json_dict()
+        )
+
+    def test_filler_component(self):
+        arg = {}
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.FILLER),
+            FillerComponent(**arg).as_json_dict()
+        )
+
+    def test_icon_component(self):
+        arg = {
+            'url': 'https://example.com/icon/png/caution.png',
+            'size': 'lg',
+            'aspect_ratio': '1.91:1'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.ICON),
+            IconComponent(**arg).as_json_dict()
+        )
+
+    def test_image_component(self):
+        arg = {
+            'url': 'https://example.com/flex/images/image.jpg',
+            'size': 'full',
+            'aspect_ratio': '1.91:1'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.IMAGE),
+            ImageComponent(**arg).as_json_dict()
+        )
+
+    def test_separator_component(self):
+        arg = {
+            'color': '#000000'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.SEPARATOR),
+            SeparatorComponent(**arg).as_json_dict()
+        )
+
+    def test_spacer_component(self):
+        arg = {
+            'size': 'md'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.SPACER),
+            SpacerComponent(**arg).as_json_dict()
+        )
+
+    def test_text_component(self):
+        arg = {
+            'text': 'Hello, World!',
+            'size': 'xl',
+            'weight': 'bold',
+            'color': '#0000ff'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.TEXT),
+            TextComponent(**arg).as_json_dict()
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/models/test_imagemap.py b/tests/models/test_imagemap.py
new file mode 100644
index 0000000..814df0e
--- /dev/null
+++ b/tests/models/test_imagemap.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the 'License'); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+
+from linebot.models import (
+    ImagemapArea,
+    ImagemapSendMessage,
+    URIImagemapAction,
+    MessageImagemapAction,
+    Video, BaseSize, ExternalLink,
+)
+from tests.models.serialize_test_case import SerializeTestCase
+
+
+class TestImageMap(SerializeTestCase):
+    def test_image_map(self):
+        arg = {
+            'base_url': 'https://example.com/bot/images/rm001',
+            'alt_text': 'This is an imagemap',
+            'base_size': BaseSize(width=1040, height=1040),
+            'video':
+                Video(
+                    original_content_url='https://example.com/video.mp4',
+                    preview_image_url='https://example.com/video_preview.jpg',
+                    area=ImagemapArea(x=0, y=0, width=1040, height=585),
+                    external_link=ExternalLink(label='See more',
+                                               link_uri='https://example.com/see_more.html')
+                ),
+            'actions': [
+                URIImagemapAction(
+                    link_uri='https://example.com/',
+                    area=ImagemapArea(x=0, y=585, width=520, height=454)
+                ),
+                MessageImagemapAction(
+                    text='Hey',
+                    area=ImagemapArea(x=0, y=58, width=52, height=40)
+                )
+            ]
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.IMAGEMAP),
+            ImagemapSendMessage(**arg).as_json_dict()
+        )
+
+    def test_video(self):
+        arg = {
+            'original_content_url': 'https://example.com/video.mp4',
+            'preview_image_url': 'https://example.com/video_preview.jpg',
+            'area': ImagemapArea(x=0, y=0, width=1040, height=585),
+            'external_link': ExternalLink(label='See more',
+                                          link_uri='https://example.com/see_more.html')
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            Video(**arg).as_json_dict()
+        )
+
+    def test_message_actions(self):
+        arg = {
+            'text': 'Hey',
+            'area': ImagemapArea(x=0, y=58, width=52, height=40)
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.MESSAGE),
+            MessageImagemapAction(**arg).as_json_dict()
+        )
+
+    def test_uri_actions(self):
+        arg = {
+            'link_uri': 'https://example.com/',
+            'area': ImagemapArea(x=0, y=585, width=520, height=454)
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, self.URI),
+            URIImagemapAction(**arg).as_json_dict()
+        )
+
+    def test_base_size(self):
+        arg = {
+            'width': 1040,
+            'height': 1040
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            BaseSize(**arg).as_json_dict()
+        )
+
+    def test_external_link(self):
+        arg = {
+            'label': 'Hey link',
+            'link_uri': 'https://example.com/see_more.html'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            ExternalLink(**arg).as_json_dict()
+        )
+
+    def test_imagemap_area(self):
+        arg = {
+            'x': 111,
+            'y': 33,
+            'width': 1040,
+            'height': 1040
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            ImagemapArea(**arg).as_json_dict()
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/models/test_send_messages.py b/tests/models/test_send_messages.py
new file mode 100644
index 0000000..0e89911
--- /dev/null
+++ b/tests/models/test_send_messages.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the 'License'); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+
+from linebot.models import (
+    TextSendMessage,
+    StickerSendMessage,
+    VideoSendMessage,
+    AudioSendMessage,
+    LocationSendMessage,
+    ImageSendMessage,
+    QuickReplyButton,
+    QuickReply,
+    CameraRollAction,
+    CameraAction
+)
+from tests.models.serialize_test_case import SerializeTestCase
+
+
+class TestSendMessages(SerializeTestCase):
+    def test_text_message(self):
+        arg = {
+            'text': 'Hello, world'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.TEXT),
+            TextSendMessage(**arg).as_json_dict()
+        )
+
+    def test_sticker_message(self):
+        arg = {
+            'package_id': '1',
+            'sticker_id': '1'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.STICKER),
+            StickerSendMessage(**arg).as_json_dict()
+        )
+
+    def test_image_message(self):
+        arg = {
+            'original_content_url': 'https://example.com/original.jpg',
+            'preview_image_url': 'https://example.com/preview.jpg'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.IMAGE),
+            ImageSendMessage(**arg).as_json_dict()
+        )
+
+    def test_video_message(self):
+        arg = {
+            'type': 'video',
+            'original_content_url': 'https://example.com/original.mp4',
+            'preview_image_url': 'https://example.com/preview.jpg'
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.VIDEO),
+            VideoSendMessage(**arg).as_json_dict()
+        )
+
+    def test_audio_message(self):
+        arg = {
+            'original_content_url': 'https://example.com/original.m4a',
+            'duration': 60000
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.AUDIO),
+            AudioSendMessage(**arg).as_json_dict()
+        )
+
+    def test_location_message(self):
+        arg = {
+            'title': 'my location',
+            'address': '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
+            'latitude': 35.65910807942215,
+            'longitude': 139.70372892916203
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.LOCATION),
+            LocationSendMessage(**arg).as_json_dict()
+        )
+
+    def test_quick_reply_button(self):
+        arg = {
+            'action': CameraRollAction(label='Send photo')
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.ACTION),
+            QuickReplyButton(**arg).as_json_dict()
+        )
+
+    def test_quick_reply(self):
+        arg = {
+            'items': [
+                QuickReplyButton(action=CameraRollAction(label='Send photo')),
+                QuickReplyButton(action=CameraAction(label='Open camera')),
+            ]
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            QuickReply(**arg).as_json_dict()
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/models/test_template.py b/tests/models/test_template.py
new file mode 100644
index 0000000..0e029d3
--- /dev/null
+++ b/tests/models/test_template.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+
+#  Licensed under the Apache License, Version 2.0 (the 'License'); you may
+#  not use this file except in compliance with the License. You may obtain
+#  a copy of the License at
+#
+#       https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+
+from __future__ import unicode_literals, absolute_import
+
+import unittest
+
+from linebot.models import (
+    TemplateSendMessage,
+    CarouselTemplate,
+    CarouselColumn,
+    ImageCarouselTemplate,
+    ImageCarouselColumn,
+    ConfirmTemplate,
+    ButtonsTemplate,
+    PostbackAction,
+    URIAction,
+    MessageAction,
+)
+from tests.models.serialize_test_case import SerializeTestCase
+
+
+class TestTemplate(SerializeTestCase):
+    def test_template(self):
+        arg = {
+            'type': 'template',
+            'alt_text': 'This is a buttons template',
+            'template':
+                ButtonsTemplate(
+                    thumbnail_image_url='https=//example.com/bot/images/image.jpg',
+                    image_aspect_ratio='rectangle',
+                    image_size='cover',
+                    image_background_color='#FFFFFF',
+                    title='Menu',
+                    text='Please select',
+                    default_action=URIAction(label='View detail',
+                                             uri='http://example.com/page/123'),
+                    actions=[
+                        PostbackAction(label='Buy', data='action=buy&itemid=155'),
+                        URIAction(label='View detail', uri='http://example.com/page/155'),
+                    ]
+                )
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.TEMPLATE),
+            TemplateSendMessage(**arg).as_json_dict()
+        )
+
+    def test_button_template(self):
+        arg = {
+            'type': 'buttons',
+            'thumbnail_image_url': 'https=//example.com/bot/images/image.jpg',
+            'image_aspect_ratio': 'rectangle',
+            'image_size': 'cover',
+            'image_background_color': '#FFFFFF',
+            'title': 'Menu',
+            'text': 'Please select',
+            'default_action': URIAction(label='View detail',
+                                        uri='http://example.com/page/123'),
+            'actions': [
+                PostbackAction(label='Buy', data='action=buy&itemid=155'),
+                URIAction(label='View detail', uri='http://example.com/page/155'),
+            ]
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.BUTTONS),
+            ButtonsTemplate(**arg).as_json_dict()
+        )
+
+    def test_confirm_template(self):
+        arg = {
+            'type': 'confirm',
+            'text': 'Are you sure?',
+            'actions': [
+                MessageAction(label='Yes', text='yes...'),
+                MessageAction(label='No', text='no...'),
+            ]
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.CONFIRM),
+            ConfirmTemplate(**arg).as_json_dict()
+        )
+
+    def test_carousel_template(self):
+        arg = {
+            'image_aspect_ratio': 'rectangle',
+            'image_size': 'cover',
+            'columns': [
+                CarouselColumn(
+                    thumbnail_image_url='https://example.com/bot/images/item1.jpg',
+                    image_background_color='#FFFFFF',
+                    title='this is menu',
+                    text='description',
+                    default_action=URIAction(label='View detail',
+                                             uri='http://example.com/page/123'),
+                    actions=[
+                        PostbackAction(label='Buy', data='action=buy&itemid=155'),
+                        URIAction(label='View detail', uri='http://example.com/page/155'),
+                    ]
+                ),
+                CarouselColumn(
+                    thumbnail_image_url='https://example.com/bot/images/item2.jpg',
+                    image_background_color='#000000',
+                    title='this is menu',
+                    text='description',
+                    default_action=URIAction(label='View detail',
+                                             uri='http://example.com/page/222'),
+                    actions=[
+                        PostbackAction(label='Buy', data='action=buy&itemid=555'),
+                        URIAction(label='View detail', uri='http://example.com/page/555'),
+                    ]
+                )
+            ]
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.CAROUSEL),
+            CarouselTemplate(**arg).as_json_dict()
+        )
+
+    def test_carousel_column(self):
+        arg = {
+            'thumbnail_image_url': 'https://example.com/bot/images/item1.jpg',
+            'image_background_color': '#FFFFFF',
+            'title': 'this is menu',
+            'text': 'description',
+            'default_action': URIAction(label='View detail',
+                                        uri='http://example.com/page/123'),
+            'actions': [
+                PostbackAction(label='Buy', data='action=buy&itemid=155'),
+                URIAction(label='View detail', uri='http://example.com/page/155'),
+            ]
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            CarouselColumn(**arg).as_json_dict()
+        )
+
+    def test_image_carousel_template(self):
+        arg = {
+            'columns': [
+                ImageCarouselColumn(
+                    image_url='https://example.com/bot/images/item1.jpg',
+                    action=PostbackAction(label='Buy', data='action=buy&itemid=555')
+                ),
+                ImageCarouselColumn(
+                    image_url='https://example.com/bot/images/item2.jpg',
+                    action=MessageAction(label='Yes', text='yes')
+                ),
+            ]
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg, type=self.IMAGE_CAROUSEL),
+            ImageCarouselTemplate(**arg).as_json_dict()
+        )
+
+    def test_image_carousel_column(self):
+        arg = {
+            'image_url': 'https://example.com/bot/images/item1.jpg',
+            'action': PostbackAction(label='Buy', data='action=buy&itemid=555')
+        }
+        self.assertEqual(
+            self.serialize_as_dict(arg),
+            ImageCarouselColumn(**arg).as_json_dict()
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()