diff --git a/docs/api/wrapper/README.md b/docs/api/wrapper/README.md
index 78910e581..b4765b6f4 100644
--- a/docs/api/wrapper/README.md
+++ b/docs/api/wrapper/README.md
@@ -8,21 +8,21 @@ A `Wrapper` is an object that contains a mounted component or vnode and methods
 
 ### `vm`
 
-`Component`: This is the `Vue` instance. You can access all the [instance methods and properties of a vm](https://vuejs.org/v2/api/#Instance-Properties) with `wrapper.vm`. This only exists on Vue component wrappers  
+`Component` (read-only): This is the `Vue` instance. You can access all the [instance methods and properties of a vm](https://vuejs.org/v2/api/#Instance-Properties) with `wrapper.vm`. This only exists on Vue component wrappers.
 
 ### `element`
 
-`HTMLElement`: the root DOM node of the wrapper  
+`HTMLElement` (read-only): the root DOM node of the wrapper
 
 ### `options` 
 
 #### `options.attachedToDocument`
 
-`Boolean`: True if `attachedToDocument` in mounting options was true  
+`Boolean` (read-only): True if `attachedToDocument` in mounting options was `true`
 
 #### `options.sync` 
 
-`Boolean`: True if `sync` in mounting options was not `false`
+`Boolean` (read-only): True if `sync` in mounting options was not `false`
 
 ## Methods
 
diff --git a/packages/test-utils/src/vue-wrapper.js b/packages/test-utils/src/vue-wrapper.js
index 3f1820f76..289790a6b 100644
--- a/packages/test-utils/src/vue-wrapper.js
+++ b/packages/test-utils/src/vue-wrapper.js
@@ -18,7 +18,11 @@ export default class VueWrapper extends Wrapper implements BaseWrapper {
       get: () => vm.$el,
       set: () => {}
     })
-    this.vm = vm
+    // $FlowIgnore
+    Object.defineProperty(this, 'vm', {
+      get: () => vm,
+      set: () => {}
+    })
     if (options.sync) {
       setWatchersToSync(vm)
       orderWatchers(vm)
diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js
index 371178cb5..a69fd7ff9 100644
--- a/packages/test-utils/src/wrapper.js
+++ b/packages/test-utils/src/wrapper.js
@@ -23,32 +23,50 @@ import createWrapper from './create-wrapper'
 import { orderWatchers } from './order-watchers'
 
 export default class Wrapper implements BaseWrapper {
-  vnode: VNode | null;
-  vm: Component | null;
+  +vnode: VNode | null;
+  +vm: Component | null;
   _emitted: { [name: string]: Array<Array<any>> };
   _emittedByOrder: Array<{ name: string, args: Array<any> }>;
   isVm: boolean;
-  element: Element;
+  +element: Element;
   update: Function;
-  options: WrapperOptions;
+  +options: WrapperOptions;
   version: number;
   isFunctionalComponent: boolean;
 
   constructor (node: VNode | Element, options: WrapperOptions) {
-    if (node instanceof Element) {
-      this.element = node
-      this.vnode = null
-    } else {
-      this.vnode = node
-      this.element = node.elm
+    const vnode = node instanceof Element ? null : node
+    const element = node instanceof Element ? node : node.elm
+    // Prevent redefine by VueWrapper
+    if (this.constructor.name === 'Wrapper') {
+      // $FlowIgnore
+      Object.defineProperty(this, 'vnode', {
+        get: () => vnode,
+        set: () => {}
+      })
+      // $FlowIgnore
+      Object.defineProperty(this, 'element', {
+        get: () => element,
+        set: () => {}
+      })
+      // $FlowIgnore
+      Object.defineProperty(this, 'vm', {
+        get: () => undefined,
+        set: () => {}
+      })
     }
+    const frozenOptions = Object.freeze(options)
+    // $FlowIgnore
+    Object.defineProperty(this, 'options', {
+      get: () => frozenOptions,
+      set: () => {}
+    })
     if (
       this.vnode &&
       (this.vnode[FUNCTIONAL_OPTIONS] || this.vnode.functionalContext)
     ) {
       this.isFunctionalComponent = true
     }
-    this.options = options
     this.version = Number(
       `${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`
     )
@@ -112,7 +130,7 @@ export default class Wrapper implements BaseWrapper {
    */
   emitted (event?: string) {
     if (!this._emitted && !this.vm) {
-      throwError(`wrapper.emitted() can only be called on a Vue ` + `instance`)
+      throwError(`wrapper.emitted() can only be called on a Vue instance`)
     }
     if (event) {
       return this._emitted[event]
@@ -126,7 +144,7 @@ export default class Wrapper implements BaseWrapper {
   emittedByOrder () {
     if (!this._emittedByOrder && !this.vm) {
       throwError(
-        `wrapper.emittedByOrder() can only be called on a ` + `Vue instance`
+        `wrapper.emittedByOrder() can only be called on a Vue instance`
       )
     }
     return this._emittedByOrder
@@ -155,13 +173,7 @@ export default class Wrapper implements BaseWrapper {
       `visible has been deprecated and will be removed in ` +
         `version 1, use isVisible instead`
     )
-
     let element = this.element
-
-    if (!element) {
-      return false
-    }
-
     while (element) {
       if (
         element.style &&
@@ -188,17 +200,17 @@ export default class Wrapper implements BaseWrapper {
 
     if (typeof attribute !== 'string') {
       throwError(
-        `wrapper.hasAttribute() must be passed attribute as ` + `a string`
+        `wrapper.hasAttribute() must be passed attribute as a string`
       )
     }
 
     if (typeof value !== 'string') {
       throwError(
-        `wrapper.hasAttribute() must be passed value as a ` + `string`
+        `wrapper.hasAttribute() must be passed value as a string`
       )
     }
 
-    return !!(this.element && this.element.getAttribute(attribute) === value)
+    return !!(this.element.getAttribute(attribute) === value)
   }
 
   /**
@@ -270,7 +282,7 @@ export default class Wrapper implements BaseWrapper {
     )
 
     if (typeof style !== 'string') {
-      throwError(`wrapper.hasStyle() must be passed style as a ` + `string`)
+      throwError(`wrapper.hasStyle() must be passed style as a string`)
     }
 
     if (typeof value !== 'string') {
@@ -413,11 +425,6 @@ export default class Wrapper implements BaseWrapper {
    */
   isVisible (): boolean {
     let element = this.element
-
-    if (!element) {
-      return false
-    }
-
     while (element) {
       if (
         element.style &&
@@ -669,41 +676,32 @@ export default class Wrapper implements BaseWrapper {
    * Sets element value and triggers input event
    */
   setValue (value: any) {
-    const el = this.element
-
-    if (!el) {
-      throwError(
-        `cannot call wrapper.setValue() on a wrapper ` + `without an element`
-      )
-    }
-
-    const tag = el.tagName
+    const tagName = this.element.tagName
     const type = this.attributes().type
-    const event = 'input'
 
-    if (tag === 'SELECT') {
+    if (tagName === 'SELECT') {
       throwError(
         `wrapper.setValue() cannot be called on a <select> ` +
           `element. Use wrapper.setSelected() instead`
       )
-    } else if (tag === 'INPUT' && type === 'checkbox') {
+    } else if (tagName === 'INPUT' && type === 'checkbox') {
       throwError(
         `wrapper.setValue() cannot be called on a <input ` +
           `type="checkbox" /> element. Use ` +
           `wrapper.setChecked() instead`
       )
-    } else if (tag === 'INPUT' && type === 'radio') {
+    } else if (tagName === 'INPUT' && type === 'radio') {
       throwError(
         `wrapper.setValue() cannot be called on a <input ` +
           `type="radio" /> element. Use wrapper.setChecked() ` +
           `instead`
       )
-    } else if (tag === 'INPUT' || tag === 'textarea') {
+    } else if (tagName === 'INPUT' || tagName === 'textarea') {
       // $FlowIgnore
-      el.value = value
-      this.trigger(event)
+      this.element.value = value
+      this.trigger('input')
     } else {
-      throwError(`wrapper.setValue() cannot be called on this ` + `element`)
+      throwError(`wrapper.setValue() cannot be called on this element`)
     }
   }
 
@@ -714,36 +712,26 @@ export default class Wrapper implements BaseWrapper {
     if (typeof checked !== 'boolean') {
       throwError('wrapper.setChecked() must be passed a boolean')
     }
-
-    const el = this.element
-
-    if (!el) {
-      throwError(
-        `cannot call wrapper.setChecked() on a wrapper ` + `without an element`
-      )
-    }
-
-    const tag = el.tagName
+    const tagName = this.element.tagName
     const type = this.attributes().type
-    const event = 'change'
 
-    if (tag === 'SELECT') {
+    if (tagName === 'SELECT') {
       throwError(
         `wrapper.setChecked() cannot be called on a ` +
           `<select> element. Use wrapper.setSelected() ` +
           `instead`
       )
-    } else if (tag === 'INPUT' && type === 'checkbox') {
+    } else if (tagName === 'INPUT' && type === 'checkbox') {
       // $FlowIgnore
-      if (el.checked !== checked) {
+      if (this.element.checked !== checked) {
         if (!navigator.userAgent.includes('jsdom')) {
           // $FlowIgnore
-          el.checked = checked
+          this.element.checked = checked
         }
         this.trigger('click')
-        this.trigger(event)
+        this.trigger('change')
       }
-    } else if (tag === 'INPUT' && type === 'radio') {
+    } else if (tagName === 'INPUT' && type === 'radio') {
       if (!checked) {
         throwError(
           `wrapper.setChecked() cannot be called with ` +
@@ -752,18 +740,18 @@ export default class Wrapper implements BaseWrapper {
         )
       } else {
         // $FlowIgnore
-        if (!el.checked) {
+        if (!this.element.checked) {
           this.trigger('click')
-          this.trigger(event)
+          this.trigger('change')
         }
       }
-    } else if (tag === 'INPUT' || tag === 'textarea') {
+    } else if (tagName === 'INPUT' || tagName === 'textarea') {
       throwError(
         `wrapper.setChecked() cannot be called on "text" ` +
           `inputs. Use wrapper.setValue() instead`
       )
     } else {
-      throwError(`wrapper.setChecked() cannot be called on this ` + `element`)
+      throwError(`wrapper.setChecked() cannot be called on this element`)
     }
   }
 
@@ -771,55 +759,46 @@ export default class Wrapper implements BaseWrapper {
    * Selects <option></option> element
    */
   setSelected () {
-    const el = this.element
-
-    if (!el) {
-      throwError(
-        `cannot call wrapper.setSelected() on a wrapper ` + `without an element`
-      )
-    }
-
-    const tag = el.tagName
+    const tagName = this.element.tagName
     const type = this.attributes().type
-    const event = 'change'
 
-    if (tag === 'OPTION') {
+    if (tagName === 'OPTION') {
       // $FlowIgnore
-      el.selected = true
+      this.element.selected = true
       // $FlowIgnore
-      if (el.parentElement.tagName === 'OPTGROUP') {
+      if (this.element.parentElement.tagName === 'OPTGROUP') {
         // $FlowIgnore
-        createWrapper(el.parentElement.parentElement, this.options).trigger(
-          event
-        )
+        createWrapper(this.element.parentElement.parentElement, this.options)
+          .trigger('change')
       } else {
         // $FlowIgnore
-        createWrapper(el.parentElement, this.options).trigger(event)
+        createWrapper(this.element.parentElement, this.options)
+          .trigger('change')
       }
-    } else if (tag === 'SELECT') {
+    } else if (tagName === 'SELECT') {
       throwError(
         `wrapper.setSelected() cannot be called on select. ` +
           `Call it on one of its options`
       )
-    } else if (tag === 'INPUT' && type === 'checkbox') {
+    } else if (tagName === 'INPUT' && type === 'checkbox') {
       throwError(
         `wrapper.setSelected() cannot be called on a <input ` +
           `type="checkbox" /> element. Use ` +
           `wrapper.setChecked() instead`
       )
-    } else if (tag === 'INPUT' && type === 'radio') {
+    } else if (tagName === 'INPUT' && type === 'radio') {
       throwError(
         `wrapper.setSelected() cannot be called on a <input ` +
           `type="radio" /> element. Use wrapper.setChecked() ` +
           `instead`
       )
-    } else if (tag === 'INPUT' || tag === 'textarea') {
+    } else if (tagName === 'INPUT' || tagName === 'textarea') {
       throwError(
         `wrapper.setSelected() cannot be called on "text" ` +
           `inputs. Use wrapper.setValue() instead`
       )
     } else {
-      throwError(`wrapper.setSelected() cannot be called on this ` + `element`)
+      throwError(`wrapper.setSelected() cannot be called on this element`)
     }
   }
 
@@ -827,12 +806,6 @@ export default class Wrapper implements BaseWrapper {
    * Return text of wrapper element
    */
   text (): string {
-    if (!this.element) {
-      throwError(
-        `cannot call wrapper.text() on a wrapper without an ` + `element`
-      )
-    }
-
     return this.element.textContent.trim()
   }
 
@@ -859,12 +832,6 @@ export default class Wrapper implements BaseWrapper {
       throwError('wrapper.trigger() must be passed a string')
     }
 
-    if (!this.element) {
-      throwError(
-        `cannot call wrapper.trigger() on a wrapper without ` + `an element`
-      )
-    }
-
     if (options.target) {
       throwError(
         `you cannot set the target value of an event. See ` +
diff --git a/test/specs/vuewrapper.js b/test/specs/vuewrapper.js
new file mode 100644
index 000000000..b909d9e31
--- /dev/null
+++ b/test/specs/vuewrapper.js
@@ -0,0 +1,13 @@
+import { describeWithShallowAndMount } from '~resources/utils'
+
+describeWithShallowAndMount('VueWrapper', mountingMethod => {
+  ['vnode', 'element', 'vm', 'options'].forEach(property => {
+    it(`has the ${property} property which is read-only`, () => {
+      const wrapper = mountingMethod({ template: '<div><p></p></div>' })
+      expect(wrapper.constructor.name).to.equal('VueWrapper')
+      const originalProperty = wrapper[property]
+      wrapper[property] = 'foo'
+      expect(wrapper[property]).to.equal(originalProperty)
+    })
+  })
+})
diff --git a/test/specs/wrapper.spec.js b/test/specs/wrapper.spec.js
new file mode 100644
index 000000000..38fec40f0
--- /dev/null
+++ b/test/specs/wrapper.spec.js
@@ -0,0 +1,14 @@
+import { describeWithShallowAndMount } from '~resources/utils'
+
+describeWithShallowAndMount('Wrapper', mountingMethod => {
+  ['vnode', 'element', 'vm', 'options'].forEach(property => {
+    it(`has the ${property} property which is read-only`, () => {
+      const wrapper = mountingMethod({ template: '<div><p></p></div>' })
+        .find('p')
+      expect(wrapper.constructor.name).to.equal('Wrapper')
+      const originalProperty = wrapper[property]
+      wrapper[property] = 'foo'
+      expect(wrapper[property]).to.equal(originalProperty)
+    })
+  })
+})
diff --git a/test/specs/wrapper/hasAttribute.spec.js b/test/specs/wrapper/hasAttribute.spec.js
index 27cdad613..416a7780b 100644
--- a/test/specs/wrapper/hasAttribute.spec.js
+++ b/test/specs/wrapper/hasAttribute.spec.js
@@ -16,13 +16,6 @@ describeWithShallowAndMount('hasAttribute', mountingMethod => {
     expect(wrapper.hasAttribute('attribute', 'value')).to.equal(false)
   })
 
-  it('returns false if wrapper element is null', () => {
-    const compiled = compileToFunctions('<div />')
-    const wrapper = mountingMethod(compiled)
-    wrapper.element = null
-    expect(wrapper.hasAttribute('attribute', 'value')).to.equal(false)
-  })
-
   it('throws an error if attribute is not a string', () => {
     const compiled = compileToFunctions('<div />')
     const wrapper = mountingMethod(compiled)
diff --git a/test/specs/wrapper/setChecked.spec.js b/test/specs/wrapper/setChecked.spec.js
index 44e3423f7..805b504b8 100644
--- a/test/specs/wrapper/setChecked.spec.js
+++ b/test/specs/wrapper/setChecked.spec.js
@@ -83,19 +83,6 @@ describeWithShallowAndMount('setChecked', mountingMethod => {
     shouldThrowErrorOnElement('#radioFoo', message, false)
   })
 
-  it('throws error if wrapper does not contain element', () => {
-    const wrapper = mountingMethod({ template: '<div><p/></div>' })
-    const p = wrapper.find('p')
-    p.element = null
-
-    const fn = () => p.setChecked()
-    const message =
-      '[vue-test-utils]: cannot call wrapper.setChecked() on a wrapper without an element'
-    expect(fn)
-      .to.throw()
-      .with.property('message', message)
-  })
-
   it('throws error if element is select', () => {
     const message =
       'wrapper.setChecked() cannot be called on a <select> element. Use wrapper.setSelected() instead'
diff --git a/test/specs/wrapper/setSelected.spec.js b/test/specs/wrapper/setSelected.spec.js
index 474f32fc8..c374af856 100644
--- a/test/specs/wrapper/setSelected.spec.js
+++ b/test/specs/wrapper/setSelected.spec.js
@@ -33,19 +33,6 @@ describeWithShallowAndMount('setSelected', mountingMethod => {
     expect(wrapper.text()).to.contain('selectA')
   })
 
-  it('throws error if wrapper does not contain element', () => {
-    const wrapper = mountingMethod({ template: '<div><p/></div>' })
-    const p = wrapper.find('p')
-    p.element = null
-
-    const fn = () => p.setSelected()
-    const message =
-      '[vue-test-utils]: cannot call wrapper.setSelected() on a wrapper without an element'
-    expect(fn)
-      .to.throw()
-      .with.property('message', message)
-  })
-
   it('throws error if element is radio', () => {
     const message =
       'wrapper.setSelected() cannot be called on a <input type="radio" /> element. Use wrapper.setChecked() instead'
diff --git a/test/specs/wrapper/setValue.spec.js b/test/specs/wrapper/setValue.spec.js
index 0727a798a..e37566010 100644
--- a/test/specs/wrapper/setValue.spec.js
+++ b/test/specs/wrapper/setValue.spec.js
@@ -19,17 +19,6 @@ describeWithShallowAndMount('setValue', mountingMethod => {
     expect(wrapper.text()).to.contain('input text awesome binding')
   })
 
-  it('throws error if wrapper does not contain element', () => {
-    const wrapper = mountingMethod({ template: '<div><p/></div>' })
-    const p = wrapper.find('p')
-    p.element = null
-    const fn = () => p.setValue('')
-    const message = '[vue-test-utils]: cannot call wrapper.setValue() on a wrapper without an element'
-    expect(fn)
-      .to.throw()
-      .with.property('message', message)
-  })
-
   it('throws error if element is select', () => {
     const message =
       'wrapper.setValue() cannot be called on a <select> element. Use wrapper.setSelected() instead'
diff --git a/test/specs/wrapper/text.spec.js b/test/specs/wrapper/text.spec.js
index 7e45f013e..2137b76a7 100644
--- a/test/specs/wrapper/text.spec.js
+++ b/test/specs/wrapper/text.spec.js
@@ -20,15 +20,4 @@ describeWithShallowAndMount('text', mountingMethod => {
 
     expect(wrapper.text()).to.equal(text)
   })
-
-  it('throws error if wrapper does not contain element', () => {
-    const wrapper = mountingMethod({ template: '<div><p/></div>' })
-    const p = wrapper.find('p')
-    p.element = null
-    const fn = () => p.text()
-    const message = '[vue-test-utils]: cannot call wrapper.text() on a wrapper without an element'
-    expect(fn)
-      .to.throw()
-      .with.property('message', message)
-  })
 })
diff --git a/test/specs/wrapper/trigger.spec.js b/test/specs/wrapper/trigger.spec.js
index 457df809b..deb8b61d0 100644
--- a/test/specs/wrapper/trigger.spec.js
+++ b/test/specs/wrapper/trigger.spec.js
@@ -157,18 +157,6 @@ describeWithShallowAndMount('trigger', mountingMethod => {
       .with.property('message', message)
   })
 
-  it('throws error if wrapper does not contain element', () => {
-    const wrapper = mountingMethod({ template: '<div><p/></div>' })
-    const p = wrapper.find('p')
-    p.element = null
-    const fn = () => p.trigger('click')
-    const message =
-      '[vue-test-utils]: cannot call wrapper.trigger() on a wrapper without an element'
-    expect(fn)
-      .to.throw()
-      .with.property('message', message)
-  })
-
   it('throws an error if type is not a string', () => {
     const wrapper = mountingMethod(ComponentWithEvents)
     const invalidSelectors = [