测试控制器正确处理唯一性验证的正确方法是什么?

如何解决测试控制器正确处理唯一性验证的正确方法是什么?

摘要

我正在构建一个包含用户注册过程的Rails应用。 usernamepassword对于在数据库中创建user对象是必需的; username必须是唯一的。 我正在寻找正确的方法来测试唯一性验证是否提示了控制器方法的特定操作,即UsersController#create

上下文

用户模型包括相关验证:

# app/models/user.rb
#
#  username        :string           not null
#  ...

class User < ApplicationRecord
  validates :username,presence: true
# ... more validations,class methods,and instance methods
end

此外,User模型的规格文件使用shoulda-matchers测试了此验证:

# spec/models/user_spec.rb

RSpec.describe User,type: :model do
  it { should validate_uniqueness_of(:username)}
# ... more model tests
end

方法UsersController#create的定义如下:

# app/controllers/users_controller.rb

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save
      render :show
    else
      flash[:errors] = @user.errors.full_messages
      redirect_to new_user_url
    end
  end
# ... more controller methods
end

自从User唯一性规范通过以来,我知道一个username请求(其中已经包含数据库中的一个POST)导致username进入条件的UsersController#create部分,我希望进行测试以验证这种情况。

当前,我以以下方式测试else如何处理UsersController#create上的唯一性验证:

username

问题

我主要关心的是# spec/controllers/users_controller_spec.rb require 'rails_helper' RSpec.describe UsersController,type: :controller do describe 'POST #create' do context "username exists in db" do before(:all) do User.create!(username: 'jarmo',password: 'good_password') end before(:each) do post :create,params: { user: { username: 'jarmo',password: 'better_password' }} end after(:all) do User.last.destroy end it "redirects to new_user_url" do expect(response).to redirect_to new_user_url end it "sets flash errors" do should set_flash[:errors] end end # ... more controller tests end before钩子。如果没有after,该测试在将来运行时将失败:无法创建新记录,因此使用相同的User.last.destroy创建 second 记录不会不会发生。

问题

我应该在控制器规格中测试这种特定的模型验证吗?如果是这样,这些挂钩是实现此目标的正确/最佳方法吗?

解决方法

我会避免对“我应该……”这一部分发表意见,但是有几个方面值得考虑。首先,尽管尚未正式弃用控制器测试,但Rails和Rspec团队通常都不建议使用它们。从RSpec 3.5 release notes

Rails团队和RSpec核心团队的官方推荐 是写请求规范。要求规格可让您专注于 单个控制器动作,但与控制器测试不同的是, 路由器,中间件堆栈以及机架请求和响应。 这为您编写的测试增加了真实感,并有助于避免 控制器规格中常见的许多问题。

该方案是否需要相应的请求规范是一个判断调用,但是如果您想在模型级别对验证进行单元测试,请查看shoulda matchers gem,这有助于进行模型验证测试。

关于钩子的问题,before(:all)钩子在数据库事务之外运行,因此即使您在RSpec配置中将use_transactional_fixtures设置为true,它们也不会自动回滚。因此,匹配的after(:all)是您要走的路。替代方法包括:

  1. before(:each)挂钩内创建用户,该挂钩确实在事务中运行并回滚。这是以某些测试性能为代价的。
  2. 使用Database Cleaner gem之类的工具,它可以使您对清理测试数据库的策略进行细粒度的控制。
,

如果您想同时介绍控制器和用户反馈方面的内容,我建议您使用一项功能规格:

RSpec.feature "User creation" do
  context "with duplicate emails" do
    let!(:user) { User.create!(username: 'jarmo',password: 'good_password') }

    it "does not allow duplicate emails" do
      visit new_user_path
      fill_in 'Email',with: user.email
      fill_in 'Password',with: 'p4ssw0rd'
      fill_in 'Password Confirmation',with: 'p4ssw0rd'
      expect do
        click_button 'Sign up'
      end.to_not change(User,:count)
      expect(page).to have_content 'Email has already been taken'
    end
  end
end

与其在控制器内部进行戳入,不如从用户故事中驱动整个堆栈,并测试视图实际上也具有针对验证错误的输出-因此,它在控制器规范所提供的价值很小的情况下提供了价值。

使用let/let!来设置特定示例的给定,因为它的优点是您可以通过示例所生成的辅助方法在示例中引用它们。除了诸如扎根API之类的内容外,通常应避免使用before(:all)。每个示例都应具有自己的设置/拆卸方法。

但是您还需要处理控制器本身已损坏的事实。它应该显示为:

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render :new,status: :unprocessable_entity
    end
  end
end

当记录无效时,您不应重定向回。在显示执行POST请求的结果时,再次渲染表单。重定向回去会带来可怕的用户体验,因为所有字段都会被清空。

成功创建资源后,应将用户重定向到新创建的资源,以便浏览器URL实际上指向新资源。如果您不重新加载页面,则会加载索引。

这也消除了在会话中填充错误消息的需要。如果您想通过闪光灯提供有用的反馈,则可以这样做:

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      flash.now[:error] = "Signup failed."
      render :new,status: :unprocessable_entity
    end
  end
end

您可以使用以下命令进行测试:

expect(page).to have_content "Signup failed."

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-