简体   繁体   English

Rails + Jasmine-Ajax:测试由`ajax:success`(jquery-ujs)触发的代码的正确方法是什么

[英]Rails + Jasmine-Ajax: what is the correct way to test code triggered by `ajax:success` (jquery-ujs)

I am trying to test a certain internal library that has some JS behavior triggered on the ajax:success event. 我正在尝试测试某个内部库,该库在ajax:success事件上触发了一些JS行为。

The library creates a link that looks like this: 该库创建一个如下所示的链接:

<%= link_to 'click here', '/some_path', class: 'special-link', remote: true %>

And in the JS part of the library there is event binding code, which is the part I want to black-box test through its effect on the DOM : 在库的JS部分有事件绑定代码, 这是我想通过它对DOM的影响进行黑盒测试的部分

$(document).on 'ajax:success', '.special-link', (e, data, status, xhr) ->
  # Code that has some effect on the DOM as a function of the server response

The library works as expected in the browser. 该库在浏览器中按预期工作。 However, when I try to test the library in Jasmine by calling $('.special-link').click() , the desirable effect on the DOM cannot be observed. 但是,当我尝试通过调用$('.special-link').click()来测试Jasmine中的库时$('.special-link').click() ,无法观察到对DOM的理想效果。

The issue, it seems, is that the ajax:success event does not get triggered: 看来,问题是ajax:success事件不会被触发:

describe 'my library', ->
  beforeEach ->
    MagicLamp.load('fixture') # Fixture library that injects the link above to the DOM
    jasmine.Ajax.install()
    jasmine.Ajax.stubRequest('/some_path').andReturn({
      responseText: 'response that is supposed to trigger some effect on the DOM'})

  afterEach ->
    jasmine.Ajax.uninstall()

  # Works. The fixtures are loading properly
  it '[sanity] loads fixtures correctly', ->
    expect($('.special-link').length).toEqual(1)

  # Works. The jquery-ujs correctly triggers an ajax request on click
  it '[sanity] triggers the ajax call', ->
    $('.special-link').click() 
    expect(jasmine.Ajax.requests.mostRecent().url).toContain('/some_path')

  # Works. Code that tests a click event-triggering seems to be supported by Jasmine
  it '[sanity] knows how to handle click events', ->
    spy = jasmine.createSpy('my spy')
    $('.special-link').on 'click', spy
    $('.special-link').click()
    expect(spy).toHaveBeenCalled()

  # Does not work. Same code from above on the desired `ajax:success` event does not work
  it 'knows how to handle ajax:success events', ->
    spy = jasmine.createSpy('my spy')
    $('.special-link').on 'ajax:success', spy
    $('.special-link').click()
    expect(spy).toHaveBeenCalled()

What is the right way to test the effect on the DOM of code that runs in ajax:success events? 测试在ajax:success中运行的代码的DOM效果的正确方法是什么ajax:success事件?

Have you tried simply spying on the ajax function? 你试过简单地监视ajax功能吗? For that, you need to use spyOn and force it to call the success event handler. 为此,您需要使用spyOn并强制它调用success事件处理程序。 That will let you test what you expect to happen when it's called. 这将让你测试你期望在它被调用时发生的事情。

it 'knows how to handle ajax:success events', ->
  spyOn($, "ajax").and.callFake( (e) ->
    e.success({});
  )

  $('.special-link').click()

  # expect some method to be called or something to be changed in the DOM

Here's how we would handle this sort of thing on my team. 以下是我们在团队中处理此类事情的方法。

it 'knows how to handle ajax:success events', ->
  spyOn($.fn, 'on');
  $('.special-link').click()
  expect($.fn.on).toHaveBeenCalledWith('ajax:success', 
                                       '.special-link'
                                       some_func);

This pattern extends well to testing other 'on' events, too. 这种模式也很适合测试其他“开启”事件。 Say we have some jQuery like this: 假设我们有一些像这样的jQuery:

$document.on('myCustomEvent', '.some_selector', somecode.custom_func);
$document.on('ajax:error', '.some_selector', somecode.failure_func);

Then we can test it using this pattern: 然后我们可以使用这种模式测试它:

beforeEach ->
  spyOn($.fn, 'on');
  somecode.init();

Testing an Ajax failure 测试Ajax失败

it('indicates failure after ajax error', ->
  expect($.fn.on).toHaveBeenCalledWith('ajax:error',
                                       '.some_selector',
                                       somecode.failure_func);

Testing Ajax was called from custom event 测试Ajax是从自定义事件调用的

it('indicates ajax call from custom event', ->
  expect($.fn.on).toHaveBeenCalledWith('myCustomEvent',
                                       '.some_selector',
                                       somecode.custom_func);

After a lot of debugging, I found a solution. 经过大量的调试,我找到了解决方案。

When I posted my question, I made 3 critical mistakes. 当我发布我的问题时,我犯了3个重大错误。

Mistake #1: jasmine.Ajax.stubRequest path is not relative 错误#1: jasmine.Ajax.stubRequest路径不是相对的

The Ajax call was not stubbed correctly, since the path should not be the relative /some_path but rather the absolute http://localhost:3000/some_path when testing from the browser. Ajax调用没有正确存根,因为路径不应该是相对/some_path ,而是从浏览器进行测试时的绝对http://localhost:3000/some_path

In other words, instead of: 换句话说,而不是:

jasmine.Ajax.stubRequest('/some_path')

I should have used the regexp version: 我应该使用正则表达式版本:

jasmine.Ajax.stubRequest(/.*\/some_path/)

Mistake #2: jasmine.Ajax.andReturn must include cotentType 错误#2: jasmine.Ajax.andReturn必须包含cotentType

Instead of: 代替:

jasmine.Ajax.stubRequest(/.*\/some_path/).andReturn({
  responseText: 'response that is supposed to trigger some effect on the DOM'})

I should have done: 我应该做的:

jasmine.Ajax.stubRequest(/.*\/some_path/).andReturn({
      contentType: 'text/html;charset=UTF-8',
      responseText: 'response that is supposed to trigger some effect on the DOM'})

Without it, the ajax:error is triggered rather than ajax:success , with a parseerror . 没有它, ajax:error被触发而不是ajax:success ,带有parseerror

Mistake #3: The ajax:success handler is called async-ly 错误#3: ajax:success处理程序被称为async-ly

These lines of code: 这些代码行:

spy = jasmine.createSpy('my spy')
$('.special-link').on 'ajax:success', spy
$('.special-link').click()
expect(spy).toHaveBeenCalled()

don't work, since the ajax:success handler that calls spy() is called asynchronously after expect(spy).toHaveBeenCalled() is reached. 不起作用,因为调用spy()ajax:success处理程序在到达expect(spy).toHaveBeenCalled()后异步调用。 You can read more about this in Jasmine documentation . 您可以在Jasmine文档中阅读更多相关信息。

Putting it all together 把它们放在一起

This is the code that works, focusing only on the last it statement which was the main intent behind the original question: 这是一个工作,只注重最后的代码it声明,原来是一个问题的背后主力的意图:

describe 'my library', ->
  beforeEach ->
    MagicLamp.load('fixture') # Fixture library that injects the link above to the DOM
    jasmine.Ajax.install()
    jasmine.Ajax.stubRequest(/.*\/some_path/).andReturn({
      contentType: 'text/html;charset=UTF-8',
      responseText: 'response that is supposed to trigger some effect on the DOM'})

  afterEach ->
    jasmine.Ajax.uninstall()

  # Restructuring the original `it` statement to allow async handling
  describe 'ajax:success event handling', ->
    spy = jasmine.createSpy('spy')

    # Ensures no `it` statement runs before `done()` is called
    beforeEach (done) ->
      $('.special-link').on 'ajax:success', ->
        spy()
        done()

      $('.special-link').click()    

    it 'knows how to handle ajax:success events', ->
      expect(spy).toHaveBeenCalled()

Hope this helps others. 希望这有助于其他人。

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

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