简体   繁体   English

开玩笑+酶+反应16: <img> src:请求未发送

[英]jest + enzyme + react16: <img> src : request not sent

I'm using jest + enzyme to test my react component "AnimateImage" which contains an image element: 我正在使用jest + enzyme来测试我的反应组件“AnimateImage”,其中包含一个图像元素:

import * as React from 'react';
import { PureComponent } from 'react';

interface Props {
    src: string;
}

class AnimateImage extends PureComponent<Props> {

    onImgLoad = (e: Event | {target: HTMLImageElement}) => {
        console.log("yes!");
    };
    render() {
        return (
                <div className="app-image-container">
                    <img
                        ref={c => {
                            if (!c) {
                                return;
                            }
                            c.onerror = function(e){
                                console.log("error:" , e);
                            }
                            if(!c.onload){
                                c.onload = this.onImgLoad;
                                if (c && c.complete && c.naturalWidth !== 0) {
                                    this.onImgLoad({
                                        target: c
                                    })
                                }
                            }
                        }}
                        src={this.props.src}
                    />
                </div>
        );
    }
}
export default AnimateImage;


test code: 测试代码:

test("image ", () => {
    const component = mount(<AnimateImage src={url_test}/>);

    expect(component).toMatchSnapshot();

    console.log("end ##################################################################");
})

the expected result: 预期的结果:

the image's onload handler is called and I can see the "yes!" 调用图像的onload处理程序,我可以看到“是的!” printed in the console. 打印在控制台中。

the real result: 真实的结果:

the image's onload handler is not called and the image's complete attribute is false. 不调用图像的onload处理程序,并且图像的完整属性为false。

my jest configuration: 我的开玩笑配置:

    verbose: true,
    transform: {
        '.(ts|tsx)': 'ts-jest'
    },
    snapshotSerializers: ['enzyme-to-json/serializer'],
    moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
    testEnvironment: "jest-environment-jsdom-fourteen",
    testEnvironmentOptions: { "resources": 'usable' },

debug step: 调试步骤:

  1. I've confirmed that the Canvas is installed successfully and works well in the jsdom. 我已经确认Canvas已成功安装并在jsdom中运行良好。

  2. the jsdom's resource-loader uses "request-promise-native" package to fetch HTTP resource. jsdom的resource-loader使用“request-promise-native”包来获取HTTP资源。 The "request-promise-native" package's core is "request" package. “request-promise-native”包的核心是“request”包。

in the "request" package, the request.js file declares a class called Request to handle HTTP request. 在“request”包中,request.js文件声明了一个名为Request的类来处理HTTP请求。

But I found that the Request.start() function is never called and the defer function is called with the request's status "abort". 但是我发现从不调用Request.start()函数,并且在请求的状态为“abort”的情况下调用defer函数。

by the way, I've put two "console.log()" in the function where the simulated "window" and "document" call "close" function and "console.log('abort')" in the place where the request is handled. 顺便说一下,我把两个“console.log()”放在模拟“窗口”和“文档”调用“关闭”功能和“console.log('abort')”的功能中请求已处理。

  1. the result shows that the jsdom "window" is closed before the real HTTP request starts outgoing and then, this request's status is set to be "abort". 结果显示jsdom“窗口”在真正的HTTP请求开始传出之前关闭,然后,此请求的状态被设置为“abort”。
bogon:  yarn test:dom
yarn run v1.10.1
$ jest --config jest.config.js
 PASS  animate-image.spec.tsx
  ✓ image (75ms)

  console.log xxxxxxxxx/animate-image.spec.tsx:34
    end ##################################################################

window close
document close
http://XXXXX.cdn.com
abort

some piece of code in the request.js, may be helpful to understand the problem: request.js中的一些代码可能有助于理解问题:

var defer = typeof setImmediate === 'undefined'
  ? process.nextTick
  : setImmediate
 defer(function () {
    if (self._aborted) {
      return
    }

    var end = function () {
      if (self._form) {
        if (!self._auth.hasAuth) {
          self._form.pipe(self)
        } else if (self._auth.hasAuth && self._auth.sentAuth) {
          self._form.pipe(self)
        }
      }
      if (self._multipart && self._multipart.chunked) {
        self._multipart.body.pipe(self)
      }
      if (self.body) {
        if (isstream(self.body)) {
          self.body.pipe(self)
        } else {
          setContentLength()
          if (Array.isArray(self.body)) {
            self.body.forEach(function (part) {
              self.write(part)
            })
          } else {
            self.write(self.body)
          }
          self.end()
        }
      } else if (self.requestBodyStream) {
        console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.')
        self.requestBodyStream.pipe(self)
      } else if (!self.src) {
        if (self._auth.hasAuth && !self._auth.sentAuth) {
          self.end()
          return
        }
        if (self.method !== 'GET' && typeof self.method !== 'undefined') {
          self.setHeader('content-length', 0)
        }
        self.end()
      }
    }

    if (self._form && !self.hasHeader('content-length')) {
      // Before ending the request, we had to compute the length of the whole form, asyncly
      self.setHeader(self._form.getHeaders(), true)
      self._form.getLength(function (err, length) {
        if (!err && !isNaN(length)) {
          self.setHeader('content-length', length)
        }
        end()
      })
    } else {
      end()
    }

    self.ntick = true
  })

Request.prototype.start = function () {
  // start() is called once we are ready to send the outgoing HTTP request.
  // this is usually called on the first write(), end() or on nextTick()
  var self = this

  if (self.timing) {
    // All timings will be relative to this request's startTime.  In order to do this,
    // we need to capture the wall-clock start time (via Date), immediately followed
    // by the high-resolution timer (via now()).  While these two won't be set
    // at the _exact_ same time, they should be close enough to be able to calculate
    // high-resolution, monotonically non-decreasing timestamps relative to startTime.
    var startTime = new Date().getTime()
    var startTimeNow = now()
  }

  if (self._aborted) {
    return
  }

  self._started = true
  self.method = self.method || 'GET'
  self.href = self.uri.href

  if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) {
    self.setHeader('content-length', self.src.stat.size)
  }
  if (self._aws) {
    self.aws(self._aws, true)
  }

  // We have a method named auth, which is completely different from the http.request
  // auth option.  If we don't remove it, we're gonna have a bad time.
  var reqOptions = copy(self)
  delete reqOptions.auth

  debug('make request', self.uri.href)

  // node v6.8.0 now supports a `timeout` value in `http.request()`, but we
  // should delete it for now since we handle timeouts manually for better
  // consistency with node versions before v6.8.0
  delete reqOptions.timeout

  try {
    self.req = self.httpModule.request(reqOptions)
  } catch (err) {
    self.emit('error', err)
    return
  }

  if (self.timing) {
    self.startTime = startTime
    self.startTimeNow = startTimeNow

    // Timing values will all be relative to startTime (by comparing to startTimeNow
    // so we have an accurate clock)
    self.timings = {}
  }

  var timeout
  if (self.timeout && !self.timeoutTimer) {
    if (self.timeout < 0) {
      timeout = 0
    } else if (typeof self.timeout === 'number' && isFinite(self.timeout)) {
      timeout = self.timeout
    }
  }

  self.req.on('response', self.onRequestResponse.bind(self))
  self.req.on('error', self.onRequestError.bind(self))
  self.req.on('drain', function () {
    self.emit('drain')
  })

  self.req.on('socket', function (socket) {
    // `._connecting` was the old property which was made public in node v6.1.0
    var isConnecting = socket._connecting || socket.connecting
    if (self.timing) {
      self.timings.socket = now() - self.startTimeNow

      if (isConnecting) {
        var onLookupTiming = function () {
          self.timings.lookup = now() - self.startTimeNow
        }

        var onConnectTiming = function () {
          self.timings.connect = now() - self.startTimeNow
        }

        socket.once('lookup', onLookupTiming)
        socket.once('connect', onConnectTiming)

        // clean up timing event listeners if needed on error
        self.req.once('error', function () {
          socket.removeListener('lookup', onLookupTiming)
          socket.removeListener('connect', onConnectTiming)
        })
      }
    }

    var setReqTimeout = function () {
      // This timeout sets the amount of time to wait *between* bytes sent
      // from the server once connected.
      //
      // In particular, it's useful for erroring if the server fails to send
      // data halfway through streaming a response.
      self.req.setTimeout(timeout, function () {
        if (self.req) {
          self.abort()
          var e = new Error('ESOCKETTIMEDOUT')
          e.code = 'ESOCKETTIMEDOUT'
          e.connect = false
          self.emit('error', e)
        }
      })
    }
    if (timeout !== undefined) {
      // Only start the connection timer if we're actually connecting a new
      // socket, otherwise if we're already connected (because this is a
      // keep-alive connection) do not bother. This is important since we won't
      // get a 'connect' event for an already connected socket.
      if (isConnecting) {
        var onReqSockConnect = function () {
          socket.removeListener('connect', onReqSockConnect)
          clearTimeout(self.timeoutTimer)
          self.timeoutTimer = null
          setReqTimeout()
        }

        socket.on('connect', onReqSockConnect)

        self.req.on('error', function (err) { // eslint-disable-line handle-callback-err
          socket.removeListener('connect', onReqSockConnect)
        })

        // Set a timeout in memory - this block will throw if the server takes more
        // than `timeout` to write the HTTP status and headers (corresponding to
        // the on('response') event on the client). NB: this measures wall-clock
        // time, not the time between bytes sent by the server.
        self.timeoutTimer = setTimeout(function () {
          socket.removeListener('connect', onReqSockConnect)
          self.abort()
          var e = new Error('ETIMEDOUT')
          e.code = 'ETIMEDOUT'
          e.connect = true
          self.emit('error', e)
        }, timeout)
      } else {
        // We're already connected
        setReqTimeout()
      }
    }
    self.emit('socket', socket)
  })

  self.emit('request', self.req)
}

I can't get the HTTP request sent to fetch the image source. 我无法获取发送的HTTP请求来获取图像源。 Thus I can't get the img.onload handler to be called. 因此我无法调用img.onload处理程序。

anyone could help me to explain this problem? 有谁能帮我解释一下这个问题?

Finally I didn't find a way to send a request successfully for loading image. 最后,我没有找到成功发送加载图像请求的方法。

My solution is: mock the HTMLImageElement's prototype in my test code: 我的解决方案是:在我的测试代码中模拟HTMLImageElement的原型:

Object.defineProperty(HTMLImageElement.prototype, 'naturalWidth', { get: () => 120 });
Object.defineProperty(HTMLImageElement.prototype, 'complete', { get: () => true });

Thus I don't need to get the real image any more and meanwhile I can finish my test case successfully. 因此,我不再需要获得真实的图像,同时我可以成功完成我的测试用例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM